public int Invoke(Dictionary<string, string> args)
        {
            var saveAsTemplate = args.Keys.Contains(Arguments.SaveTemplate) &&
                                 Boolean.Parse(args[Arguments.SaveTemplate]);
            var credentials = new Credentials(args[Arguments.Username], args[Arguments.Password]);
            var configName = args[Arguments.ConfigName];
            var logger = LoggerFactory.GetLogger();

            // Deserialize the persisted values from a previous run. The previous run would have saved off
            // a config URL, any IDs for reference, among other things.
            var configState = new ConfigurationState();
            var config = configState.Deserialize(configName);

            // FUTURE: May want to consider cleaning up the interim persistence XML file here after every run,
            // although it can be useful for debugging purposes.

            // FUTURE: Use the log file path in the persisted data to append to an existing log file instead of
            // creating a new one. Note that this will need to be done at the program level as opposed to individual
            // commands, which is more work. Suggest moving the deserialization logic earlier (program level) and
            // setting up the log file once.

            logger.LogInfo(config.ToString());

            // Saving a template will automatically put the configuration into a suspended state. If a template is
            // not being saved, then force the suspended state. Although not strictly necessary, this makes deleting
            // connections and subsequently shutdown more robust.
            if (saveAsTemplate)
            {
                SkytapApi.SaveAsSkytapTemplate(credentials, config.ConfigurationUrl);
            }
            else
            {
                SkytapApi.SetConfigurationState(credentials, config.ConfigurationUrl, ConfigurationStates.Suspended);
            }

            // Wait for Skytap to return the expected configuration state. Do this with a retry block
            // to test for the desired state every second for 5 minutes.
            Retry.Execute(() => SkytapApi.CheckConfigurationForDesiredState(credentials, config.ConfigurationUrl,
                                                                                ConfigurationStates.Suspended),
                                                                                NumRetriesCheckConfigState,
                                                                                _retryIntervalCheckConfigState);

            // Prior to deleting a configuration, query the current connection state and disconnect
            // any existing ICNR or VPN connection. This helps prevent state issues on the Skytap side
            // with not being able to recreate the configuration once again.
            CleanupConnections(credentials, config);

            // HACKHACK: this state check is an attempted workaround at resolving a response not received when deleting a configuration
            Retry.Execute(() => SkytapApi.CheckConfigurationForDesiredState(credentials, config.ConfigurationUrl,
                                                                                ConfigurationStates.Suspended),
                                                                                NumRetriesCheckConfigState,
                                                                                _retryIntervalCheckConfigState);

            // NOTE: It was determined by the engineering team that shutting down the configuration need not
            // be in a finally block. If the configuration does not shut down due to a previous error, we can
            // live with it.
            SkytapApi.ShutDownConfiguration(credentials, config.ConfigurationUrl);

            return CommandResults.Success;
        }