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;
        }
        public int Invoke(Dictionary<string, string> args)
        {
            var credentials = new Credentials(args[Arguments.Username], args[Arguments.Password]);
            var configId = args.ContainsKey(Arguments.ConfigId) ? args[Arguments.ConfigId] : null;
            var vpnId = args.ContainsKey(Arguments.VpnId) ? args[Arguments.VpnId] : null;
            var templateId = args[Arguments.TemplateId];
            var configName = args[Arguments.ConfigName];
            var configState = new ConfigurationState();

            var logger = LoggerFactory.GetLogger();

            // CreateConfiguration (returns ConfigID of the instantiated template)
            SkytapConfiguration newTargetConfig = SkytapApi.CreateConfiguration(credentials, templateId, configName);

            // 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, newTargetConfig.ConfigurationUrl,
                                                                             new List<string> {ConfigurationStates.Suspended, ConfigurationStates.Stopped}),
                                                                             NumRetriesCheckConfigState,
                                                                             _retryIntervalCheckConfigState);

            // If VPNID supplied, make a VPN connection, if a configid, create ICNR Connection
            if (String.IsNullOrEmpty(vpnId))
            {
                // CreateIcnrConnection (between the TFSConfigNetwork and the newly instantiated config. Note that it
                // is important that the source network ID be the new configuration and the target network ID be
                // the (existing) TFS configuration. If the two are reversed, a 409 (conflict) error may occur.
                var tfsConfigNetworkId = SkytapApi.GetNetworkIdInConfiguration(credentials, configId);
                var icnrId = SkytapApi.CreateIcnrConnection(credentials, newTargetConfig.ConfigurationNetworkId, tfsConfigNetworkId );
                newTargetConfig.IcnrId = icnrId;
            }
            else
            {
                SkytapApi.AttachVpnConnection(credentials, newTargetConfig.ConfigurationUrl, newTargetConfig.ConfigurationNetworkId, vpnId);
                SkytapApi.ConnectVpn(credentials, newTargetConfig.ConfigurationUrl, newTargetConfig.ConfigurationNetworkId, vpnId);
                newTargetConfig.VpnId = vpnId;
            }

            // Need to wait again for ICNR or VPN to complete.
            //
            // Before starting the configuration, ensure that it is suspended. If it is not suspended (and perhaps stopped)
            // wait until it is in the desired state.
            var configurationState = SkytapApi.GetConfigurationState(credentials, newTargetConfig.ConfigurationUrl);

            if (configurationState != ConfigurationStates.Running)
            {
                // Attempt to start up the configuration using retry semantics just in case the first request doesn't work. This
                // could happen if the configuration tries to restart but the service is busy so the state is returned to
                // suspended and we need to retry the start-up.
                Retry.Execute(() =>
                    {
                        // Start up the configuration by changing its state to running. It is assumed there is a change to the
                        // runstate at this point, either to "busy" or "running". If the configuration ends up as "running",
                        // nothing else to do - just continue. If the state goes back to "suspended", need to retry the start
                        // logic a few more times until the number of retries is exhausted. If it never comes back from "busy" or
                        // enters some other unknown state, just exit once the retry threshold is reached.
                        SkytapApi.SetConfigurationState(credentials, newTargetConfig.ConfigurationUrl, ConfigurationStates.Running);

                        Retry.Execute(() => SkytapApi.CheckConfigurationForDesiredState(credentials, newTargetConfig.ConfigurationUrl,
                                                                            new List<string> { ConfigurationStates.Running, ConfigurationStates.Suspended }),
                                                                            NumRetriesCheckConfigState, _retryIntervalCheckConfigState);

                        // Re-get the configuration state so we can determine whether to give up attempting to start the
                        // configuration or try again. The exception will trigger a retry.
                        var currentConfigState = SkytapApi.GetConfigurationState(credentials, newTargetConfig.ConfigurationUrl);
                        if (currentConfigState == ConfigurationStates.Suspended)
                        {
                            throw new ApplicationException(Resources.TfsStartup_UnexpectedReversionToSuspended);
                        }
                    },
                    NumRetriesStartConfig, _retryIntervalStartConfig);
            }

            // Persist the log file path so that successive invocations of the EXE can use the
            // same log file.
            newTargetConfig.LogFilePath = ((TraceLogger) logger).LogFilePath;

            // Store Config Url so that we can run shutdown on it later since TFS isn't smart enough to do this for us
            var configStatePath = configState.Serialize(newTargetConfig);
            logger.LogInfo("Persisted configuration path: " + configStatePath);
            logger.LogInfo(newTargetConfig.ToString());

            return CommandResults.Success;
        }