Przeglądaj źródła

Issue 2342 – Command-line interface for installing public key into server

https://winscp.net/tracker/2342

Source commit: 4dee00b059312367058fa5bcccac2cc187ad14a3
Martin Prikryl 8 miesięcy temu
rodzic
commit
3dcb30ca22

+ 15 - 4
source/core/Script.cpp

@@ -307,11 +307,12 @@ __fastcall TScript::TScript(bool LimitedOutput)
   FLoggingTerminal = NULL;
   FGroups = false;
   FWantsProgress = false;
-  FInteractive = false;
+  FUsageWarnings = true;
   FOnTransferOut = NULL;
   FOnTransferIn = NULL;
   FIncludeFileMaskOptionUsed = false;
   FPendingLogLines = new TStringList();
+  FPrintInformation = false;
 
   Init();
 }
@@ -380,6 +381,7 @@ void __fastcall TScript::Init()
   FCommands->Register(L"echo", SCRIPT_ECHO_DESC, SCRIPT_ECHO_HELP, &EchoProc, -1, -1, true);
   FCommands->Register(L"stat", SCRIPT_STAT_DESC, SCRIPT_STAT_HELP, &StatProc, 1, 1, false);
   FCommands->Register(L"checksum", SCRIPT_CHECKSUM_DESC, SCRIPT_CHECKSUM_HELP, &ChecksumProc, 2, 2, false);
+  FCommands->Register(COPYID_COMMAND, 0, 0, &CopyIdProc, 1, 1, false);
 }
 //---------------------------------------------------------------------------
 void __fastcall TScript::RequireParams(TScriptProcParams * Parameters, int MinParams)
@@ -1246,6 +1248,15 @@ void __fastcall TScript::ChecksumProc(TScriptProcParams * Parameters)
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TScript::CopyIdProc(TScriptProcParams * Parameters)
+{
+  CheckSession();
+
+  UnicodeString FileName = Parameters->Param[1];
+  TAutoFlag AutoFlag(FPrintInformation);
+  FTerminal->UploadPublicKey(FileName);
+}
+//---------------------------------------------------------------------------
 void __fastcall TScript::TerminalCaptureLog(const UnicodeString & AddedLine,
   TCaptureOutputType OutputType)
 {
@@ -2218,7 +2229,7 @@ __fastcall TManagementScript::TManagementScript(TStoredSessionList * StoredSessi
 
   OnTerminalSynchronizeDirectory = TerminalSynchronizeDirectory;
 
-  FCommands->Register(L"exit", SCRIPT_EXIT_DESC, SCRIPT_EXIT_HELP, &ExitProc, 0, 0, false);
+  FCommands->Register(EXIT_COMMAND, SCRIPT_EXIT_DESC, SCRIPT_EXIT_HELP, &ExitProc, 0, 0, false);
   FCommands->Register(L"bye", 0, SCRIPT_EXIT_HELP, &ExitProc, 0, 0, false);
   FCommands->Register(L"open", SCRIPT_OPEN_DESC, SCRIPT_OPEN_HELP11, &OpenProc, 0, -1, true);
   FCommands->Register(L"close", SCRIPT_CLOSE_DESC, SCRIPT_CLOSE_HELP, &CloseProc, 0, 1, false);
@@ -2315,7 +2326,7 @@ void __fastcall TManagementScript::TerminalInformation(
   TTerminal * ATerminal, const UnicodeString & Str, int Phase, const UnicodeString & DebugUsedArg(Additional))
 {
   DebugAssert(ATerminal != NULL);
-  if ((Phase < 0) && (ATerminal->Status == ssOpening))
+  if ((Phase < 0) && ((ATerminal->Status == ssOpening) || FPrintInformation))
   {
     PrintLine(Str, false, ATerminal);
   }
@@ -2765,7 +2776,7 @@ void __fastcall TManagementScript::Connect(const UnicodeString Session,
         TScriptCommands::CheckParams(Options, false);
       }
 
-      if (!Session.IsEmpty() && (Data->Source != ::ssNone) && (Batch != TScript::BatchOff) && !Interactive)
+      if (!Session.IsEmpty() && (Data->Source != ::ssNone) && (Batch != TScript::BatchOff) && UsageWarnings)
       {
         std::unique_ptr<TSessionData> DataWithFingerprint(Data->Clone());
         DataWithFingerprint->LookupLastFingerprint();

+ 7 - 2
source/core/Script.h

@@ -75,7 +75,7 @@ public:
   __property TTerminal * Terminal = { read = FTerminal };
   __property bool Groups = { read = FGroups, write = FGroups };
   __property bool WantsProgress = { read = FWantsProgress, write = FWantsProgress };
-  __property bool Interactive = { read = FInteractive, write = FInteractive };
+  __property bool UsageWarnings = { read = FUsageWarnings, write = FUsageWarnings };
   __property TTransferOutEvent OnTransferOut = { read = FOnTransferOut, write = FOnTransferOut };
   __property TTransferInEvent OnTransferIn = { read = FOnTransferIn, write = FOnTransferIn };
 
@@ -105,12 +105,13 @@ protected:
   int FInteractiveSessionReopenTimeout;
   bool FGroups;
   bool FWantsProgress;
-  bool FInteractive;
+  bool FUsageWarnings;
   TTransferOutEvent FOnTransferOut;
   TTransferInEvent FOnTransferIn;
   TStrings * FPendingLogLines;
   bool FWarnNonDefaultCopyParam;
   bool FWarnNonDefaultSynchronizeParams;
+  bool FPrintInformation;
 
   virtual void __fastcall ResetTransfer();
   virtual void __fastcall ConnectTerminal(TTerminal * ATerminal);
@@ -160,6 +161,7 @@ protected:
   void __fastcall EchoProc(TScriptProcParams * Parameters);
   void __fastcall StatProc(TScriptProcParams * Parameters);
   void __fastcall ChecksumProc(TScriptProcParams * Parameters);
+  void __fastcall CopyIdProc(TScriptProcParams * Parameters);
 
   void __fastcall OptionImpl(UnicodeString OptionName, UnicodeString ValueName);
   void __fastcall SynchronizeDirectories(TScriptProcParams * Parameters,
@@ -274,4 +276,7 @@ protected:
   void __fastcall LLsProc(TScriptProcParams * Parameters);
 };
 //---------------------------------------------------------------------------
+#define COPYID_COMMAND L"copyid"
+#define EXIT_COMMAND L"exit"
+//---------------------------------------------------------------------------
 #endif

+ 24 - 7
source/core/Terminal.cpp

@@ -8923,8 +8923,19 @@ TRemoteFile * TTerminal::CheckRights(const UnicodeString & EntryType, const Unic
   return File.release();
 }
 //---------------------------------------------------------------------------
+void TTerminal::LogAndInformation(const UnicodeString & S)
+{
+  LogEvent(S);
+  Information(S);
+}
+//---------------------------------------------------------------------------
 UnicodeString TTerminal::UploadPublicKey(const UnicodeString & FileName)
 {
+  if (FSProtocol != cfsSFTP)
+  {
+    NotSupported();
+  }
+
   UnicodeString Result;
 
   UnicodeString TemporaryDir;
@@ -8944,7 +8955,7 @@ UnicodeString TTerminal::UploadPublicKey(const UnicodeString & FileName)
     bool UnusedHasCertificate;
     UnicodeString Line = GetPublicKeyLine(FileName, Comment, UnusedHasCertificate);
 
-    LogEvent(FORMAT(L"Adding public key line to \"%s\" file:\n%s", (AuthorizedKeysFilePath, Line)));
+    LogAndInformation(FMTLOAD(PUBLIC_KEY_ADDING, (AuthorizedKeysFilePath)) + L"\n" + Line);
 
     UnicodeString SshFolderAbsolutePath = UnixIncludeTrailingBackslash(GetHomeDirectory()) + OpensshFolderName;
     bool WrongRights = false;
@@ -8982,7 +8993,7 @@ UnicodeString TTerminal::UploadPublicKey(const UnicodeString & FileName)
       AuthorizedKeysFileFile->FullFileName = AuthorizedKeysFileAbsolutePath;
       std::unique_ptr<TStrings> Files(new TStringList());
       Files->AddObject(AuthorizedKeysFileAbsolutePath, AuthorizedKeysFileFile.get());
-      LogEvent(FORMAT(L"Downloading current \"%s\" file...", (OpensshAuthorizedKeysFileName)));
+      LogAndInformation(FMTLOAD(PUBLIC_KEY_DOWNLOADING, (OpensshAuthorizedKeysFileName)));
       CopyToLocal(Files.get(), TemporaryDir, &CopyParam, cpNoConfirmation, NULL);
       // Overload with Encoding parameter work incorrectly, when used on a file without BOM
       AuthorizedKeys = TFile::ReadAllText(TemporaryAuthorizedKeysFile);
@@ -8998,14 +9009,14 @@ UnicodeString TTerminal::UploadPublicKey(const UnicodeString & FileName)
       {
         if (StartsStr(Prefix, AuthorizedKeysLines->Strings[Index]))
         {
-          LogEvent(FORMAT(L"\"%s\" file already contains public key line:\n%s", (OpensshAuthorizedKeysFileName, AuthorizedKeysLines->Strings[Index])));
+          LogAndInformation(FMTLOAD(PUBLIC_KEY_CONTAINS, (OpensshAuthorizedKeysFileName)) + L"\n" + AuthorizedKeysLines->Strings[Index]);
           Updated = false;
         }
       }
 
       if (Updated)
       {
-        LogEvent(FORMAT(L"\"%s\" file does not contain the public key line yet.", (OpensshAuthorizedKeysFileName)));
+        LogAndInformation(FMTLOAD(PUBLIC_KEY_NOT_CONTAINS, (OpensshAuthorizedKeysFileName)));
         if (!EndsStr(L"\n", AuthorizedKeys))
         {
           LogEvent(FORMAT(L"Adding missing trailing new line to \"%s\" file...", (OpensshAuthorizedKeysFileName)));
@@ -9015,7 +9026,7 @@ UnicodeString TTerminal::UploadPublicKey(const UnicodeString & FileName)
     }
     else
     {
-      LogEvent(FORMAT(L"Creating new \"%s\" file...", (OpensshAuthorizedKeysFileName)));
+      LogAndInformation(FMTLOAD(PUBLIC_KEY_NEW, (OpensshAuthorizedKeysFileName)));
       CopyParam.PreserveRights = true;
       CopyParam.Rights.Number = TRights::rfUserRead | TRights::rfUserWrite;
     }
@@ -9027,14 +9038,20 @@ UnicodeString TTerminal::UploadPublicKey(const UnicodeString & FileName)
       TFile::WriteAllText(TemporaryAuthorizedKeysFile, AuthorizedKeys);
       std::unique_ptr<TStrings> Files(new TStringList());
       Files->Add(TemporaryAuthorizedKeysFile);
-      LogEvent(FORMAT(L"Uploading updated \"%s\" file...", (OpensshAuthorizedKeysFileName)));
+      LogAndInformation(FMTLOAD(PUBLIC_KEY_UPLOADING, (OpensshAuthorizedKeysFileName)));
       CopyToRemote(Files.get(), SshFolderAbsolutePath, &CopyParam, cpNoConfirmation, NULL);
     }
 
     Result = FMTLOAD(PUBLIC_KEY_UPLOADED, (Comment));
+    if (Updated)
+    {
+      LogAndInformation(ReplaceStr(RemoveMainInstructionsTag(Result), L"\n\n", L"\n"));
+    }
     if (WrongRights)
     {
-      Result += L"\n\n" + FMTLOAD(PUBLIC_KEY_PERMISSIONS, (AuthorizedKeysFilePath));
+      UnicodeString PermissionsInfo = FMTLOAD(PUBLIC_KEY_PERMISSIONS, (AuthorizedKeysFilePath));
+      LogAndInformation(PermissionsInfo);
+      Result += L"\n\n" + PermissionsInfo;
     }
   }
   __finally

+ 1 - 0
source/core/Terminal.h

@@ -263,6 +263,7 @@ private:
   bool __fastcall GetStoredCredentialsTried();
   inline bool __fastcall InTransaction();
   void __fastcall SaveCapabilities(TFileSystemInfo & FileSystemInfo);
+  void LogAndInformation(const UnicodeString & S);
   static UnicodeString __fastcall SynchronizeModeStr(TSynchronizeMode Mode);
   static UnicodeString __fastcall SynchronizeParamsStr(int Params);
 

+ 6 - 0
source/resource/TextsCore.h

@@ -542,6 +542,12 @@
 #define TIME_RELATIVE           569
 #define DAYS_SPAN               570
 #define INI_SELECT              571
+#define PUBLIC_KEY_ADDING       572
+#define PUBLIC_KEY_DOWNLOADING  573
+#define PUBLIC_KEY_CONTAINS     574
+#define PUBLIC_KEY_NOT_CONTAINS 575
+#define PUBLIC_KEY_NEW          576
+#define PUBLIC_KEY_UPLOADING    577
 
 #define CORE_VARIABLE_STRINGS   600
 #define PUTTY_BASED_ON          601

+ 6 - 0
source/resource/TextsCore1.rc

@@ -515,6 +515,12 @@ BEGIN
   TIME_RELATIVE, "just now|today|yesterday|tomorrow|one second ago|%d seconds ago|one minute ago|%d minutes ago|one hour ago|%d hours ago|one day ago|%d days ago|one week ago|%d weeks ago|one month ago|%d months ago|one year ago|%d years ago"
   DAYS_SPAN, "%d days"
   INI_SELECT, "Browse for INI file to import sites from."
+  PUBLIC_KEY_ADDING, "Adding public key line to \"%s\" file:"
+  PUBLIC_KEY_DOWNLOADING, L"Downloading current \"%s\" file..."
+  PUBLIC_KEY_CONTAINS, "\"%s\" file already contains public key line:"
+  PUBLIC_KEY_NOT_CONTAINS, "\"%s\" file does not contain the public key line yet."
+  PUBLIC_KEY_NEW, "Creating new \"%s\" file..."
+  PUBLIC_KEY_UPLOADING, "Uploading updated \"%s\" file..."
 
   CORE_VARIABLE_STRINGS, "CORE_VARIABLE"
   PUTTY_BASED_ON, "SSH and SCP code based on PuTTY %s"

+ 2 - 0
source/resource/TextsWin.h

@@ -91,6 +91,7 @@
 #define JUMPLIST_ERROR          1210
 #define TOO_MANY_PARAMS_ERROR   1211
 #define UPDATE_MISSING_ADDRESS3 1212
+#define COPYID_IDETITY_MISSING  1213
 
 #define WIN_CONFIRMATION_STRINGS 1300
 #define CONFIRM_OVERWRITE_SESSION 1301
@@ -312,6 +313,7 @@
 #define UPDATE_CURRENT          1599
 #define PATTERNS_HINT_K         7000
 #define PATTERNS_HINT_BACKSLASH 7001
+#define USAGE_COPYID            7002
 
 #define WIN_FORMS_STRINGS       1600
 #define COPY_FILE               1605

+ 2 - 0
source/resource/TextsWin1.rc

@@ -99,6 +99,7 @@ BEGIN
         DD_TARGET_UNKNOWN, "**WinSCP was not able to detect a folder, where the dragged file(s) was dropped.** In the default drag&drop mode, WinSCP allows dropping files only to local drives and mapped network drives.\n\nYou can allow dropping files to other targets in preferences. Press Help button for details."
         JUMPLIST_ERROR, "Error updating jump list."
         TOO_MANY_PARAMS_ERROR, "Too many parameters."
+        COPYID_IDETITY_MISSING, "Identity/key file was not specified."
 
         WIN_CONFIRMATION_STRINGS, "WIN_CONFIRMATION"
         CONFIRM_OVERWRITE_SESSION, "Site with name '%s' already exists. Overwrite?"
@@ -317,6 +318,7 @@ BEGIN
         UPDATE_CURRENT, "Your version: %s"
         PATTERNS_HINT_K, "!K expands to current session private key path"
         PATTERNS_HINT_BACKSLASH, "!\\ expands to current local path"
+        USAGE_COPYID, "Installs your public key in a server's authorized_keys."
 
         WIN_FORMS_STRINGS, "WIN_FORMS_STRINGS"
         COPY_FILE, "%s file '%s' to %s:"

+ 40 - 19
source/windows/ConsoleRunner.cpp

@@ -1159,8 +1159,9 @@ public:
   TConsoleRunner(TConsole * Console);
   ~TConsoleRunner();
 
-  int __fastcall Run(const UnicodeString Session, TOptions * Options,
-    TStrings * ScriptCommands, TStrings * ScriptParameters);
+  int Run(
+    const UnicodeString Session, TOptions * Options, TStrings * ScriptCommands, TStrings * ScriptParameters,
+    bool UsageWarnings);
   void __fastcall ShowException(Exception * E);
   inline void __fastcall PrintMessage(const UnicodeString & Str, bool Error = false);
 
@@ -2095,8 +2096,9 @@ void __fastcall TConsoleRunner::Failed(bool & AnyError)
   AnyError = true;
 }
 //---------------------------------------------------------------------------
-int __fastcall TConsoleRunner::Run(const UnicodeString Session, TOptions * Options,
-  TStrings * ScriptCommands, TStrings * ScriptParameters)
+int TConsoleRunner::Run(
+  const UnicodeString Session, TOptions * Options, TStrings * ScriptCommands, TStrings * ScriptParameters,
+  bool UsageWarnings)
 {
   int ExitCode;
   try
@@ -2119,7 +2121,7 @@ int __fastcall TConsoleRunner::Run(const UnicodeString Session, TOptions * Optio
       FScript->OnQueryCancel = ScriptQueryCancel;
       FScript->OnSynchronizeStartStop = ScriptSynchronizeStartStop;
       FScript->OnProgress = ScriptProgress;
-      FScript->Interactive = (ScriptCommands == NULL) && FConsole->HasFlag(cfInteractive);
+      FScript->UsageWarnings = UsageWarnings;
       if (FConsole->HasFlag(cfStdOut))
       {
         FScript->OnTransferOut = ScriptTransferOut;
@@ -2137,7 +2139,7 @@ int __fastcall TConsoleRunner::Run(const UnicodeString Session, TOptions * Optio
 
       if (!Session.IsEmpty())
       {
-        if (!FScript->Interactive)
+        if (UsageWarnings)
         {
           PrintMessage(LoadStr(SCRIPT_CMDLINE_SESSION));
         }
@@ -2344,6 +2346,8 @@ void __fastcall Usage(TConsole * Console)
     LowerCase(KEYGEN_SWITCH), LowerCase(KEYGEN_OUTPUT_SWITCH), LowerCase(KEYGEN_CHANGE_PASSPHRASE_SWITCH))));
   PrintUsageSyntax(Console, FORMAT(L"/%s keyfile [/%s=<text>] [/%s=<file>]",
     (LowerCase(KEYGEN_SWITCH), LowerCase(KEYGEN_COMMENT_SWITCH), LowerCase(KEYGEN_CERTIFICATE_SWITCH))));
+  PrintUsageSyntax(Console, FORMAT(L"/%s /%s=<file> mysession", (
+    LowerCase(COPYID_SWITCH), LowerCase(IDENTITY_SWITCH))));
   if (!CommandLineOnly)
   {
     PrintUsageSyntax(Console, L"/update");
@@ -2408,6 +2412,7 @@ void __fastcall Usage(TConsole * Console)
       TProgramParams::FormatSwitch(LowerCase(KEYGEN_COMMENT_SWITCH)) + L"=",
       TProgramParams::FormatSwitch(LowerCase(KEYGEN_CERTIFICATE_SWITCH)) + L"="));
   RegisterSwitch(SwitchesUsage, TProgramParams::FormatSwitch(KEYGEN_SWITCH), KeyGenDesc);
+  RegisterSwitch(SwitchesUsage, TProgramParams::FormatSwitch(COPYID_SWITCH), USAGE_COPYID);
   if (!CommandLineOnly)
   {
     RegisterSwitch(SwitchesUsage, L"/update", USAGE_UPDATE);
@@ -2535,8 +2540,8 @@ int __fastcall BatchSettings(TConsole * Console, TProgramParams * Params)
   return Result;
 }
 //---------------------------------------------------------------------------
-bool __fastcall FindPuttygenCompatibleSwitch(
-  TProgramParams * Params, const UnicodeString & Name, const UnicodeString & PuttygenName, UnicodeString & Value, bool & Set)
+bool FindNixCompatibleSwitch(
+  TProgramParams * Params, const UnicodeString & Name, const UnicodeString & NixName, UnicodeString & Value, bool & Set)
 {
   bool Result =
     !Name.IsEmpty() &&
@@ -2544,7 +2549,7 @@ bool __fastcall FindPuttygenCompatibleSwitch(
   if (!Result)
   {
     std::unique_ptr<TStrings> Args(new TStringList());
-    Result = Params->FindSwitchCaseSensitive(PuttygenName, Args.get(), 1);
+    Result = Params->FindSwitchCaseSensitive(NixName, Args.get(), 1);
     if (Result && (Args->Count >= 1))
     {
       Value = Args->Strings[0];
@@ -2573,29 +2578,28 @@ int __fastcall KeyGen(TConsole * Console, TProgramParams * Params)
 
     bool ValueSet;
     UnicodeString OutputFileName;
-    FindPuttygenCompatibleSwitch(Params, KEYGEN_OUTPUT_SWITCH, L"o", OutputFileName, ValueSet);
+    FindNixCompatibleSwitch(Params, KEYGEN_OUTPUT_SWITCH, L"o", OutputFileName, ValueSet);
 
     UnicodeString NewComment;
-    FindPuttygenCompatibleSwitch(Params, KEYGEN_COMMENT_SWITCH, L"C", NewComment, ValueSet);
+    FindNixCompatibleSwitch(Params, KEYGEN_COMMENT_SWITCH, L"C", NewComment, ValueSet);
 
     bool ChangePassphrase;
     bool NewPassphraseSet;
     if (Params->FindSwitchCaseSensitive(L"P"))
     {
       ChangePassphrase = true;
-      FindPuttygenCompatibleSwitch(Params, EmptyStr, L"-new-passphrase", NewPassphrase, NewPassphraseSet);
+      FindNixCompatibleSwitch(Params, EmptyStr, L"-new-passphrase", NewPassphrase, NewPassphraseSet);
     }
     else
     {
       ChangePassphrase = Params->FindSwitch(KEYGEN_CHANGE_PASSPHRASE_SWITCH, NewPassphrase, NewPassphraseSet);
     }
 
-    bool CertificateSet;
     UnicodeString Certificate;
     // It's --certificate in puttygen
-    FindPuttygenCompatibleSwitch(Params, KEYGEN_CERTIFICATE_SWITCH, L"-certificate", Certificate, CertificateSet);
+    FindNixCompatibleSwitch(Params, KEYGEN_CERTIFICATE_SWITCH, L"-certificate", Certificate, ValueSet);
 
-    FindPuttygenCompatibleSwitch(Params, PassphraseOption, L"-old-passphrase", Passphrase, ValueSet);
+    FindNixCompatibleSwitch(Params, PassphraseOption, L"-old-passphrase", Passphrase, ValueSet);
 
     if (Params->ParamCount > 0)
     {
@@ -2978,7 +2982,20 @@ int __fastcall Console(TConsoleMode Mode)
         if (CheckSafe(Params))
         {
           UnicodeString Value;
-          if (Params->FindSwitch(SCRIPT_SWITCH, Value) && !Value.IsEmpty())
+          if (Mode == cmCopyId)
+          {
+            Configuration->Usage->Inc(L"CopyId");
+            UnicodeString IdentityFileName;
+            bool ValueSet;
+            FindNixCompatibleSwitch(Params, IDENTITY_SWITCH, L"i", IdentityFileName, ValueSet);
+            if (IdentityFileName.IsEmpty())
+            {
+              throw Exception(LoadStr(COPYID_IDETITY_MISSING));
+            }
+            ScriptCommands->Add(FORMAT(L"%s \"%s\"", (COPYID_COMMAND, IdentityFileName)));
+            ScriptCommands->Add(EXIT_COMMAND);
+          }
+          else if (Params->FindSwitch(SCRIPT_SWITCH, Value) && !Value.IsEmpty())
           {
             Configuration->Usage->Inc(L"ScriptFile");
             LoadScriptFromFile(Value, ScriptCommands);
@@ -3016,15 +3033,19 @@ int __fastcall Console(TConsoleMode Mode)
         CheckLogParam(Params);
         CheckXmlLogParam(Params);
 
-        Result = Runner->Run(Session, Params,
-          (ScriptCommands->Count > 0 ? ScriptCommands : NULL),
-          ScriptParameters);
+        bool UsageWarnings = (Mode != cmCopyId) && ((ScriptCommands->Count > 0) || !Console->HasFlag(cfInteractive));
+        Result = Runner->Run(Session, Params, ScriptCommands, ScriptParameters, UsageWarnings);
       }
       catch(Exception & E)
       {
         Runner->ShowException(&E);
         Result = RESULT_ANY_ERROR;
       }
+
+      if (Mode == cmCopyId)
+      {
+        Console->WaitBeforeExit();
+      }
     }
   }
   __finally

+ 3 - 0
source/windows/WinInterface.h

@@ -56,6 +56,8 @@ const int mpAllowContinueOnError = 0x02;
 #define STDIN_SWITCH L"StdIn"
 #define STDINOUT_BINARY_VALUE L"binary"
 #define STDINOUT_CHUNKED_VALUE L"chunked"
+#define COPYID_SWITCH L"CopyId"
+#define IDENTITY_SWITCH L"Identity"
 
 #define DUMPCALLSTACK_EVENT L"WinSCPCallstack%d"
 
@@ -459,6 +461,7 @@ extern const UnicodeString OKButtonName;
 enum TConsoleMode
 {
   cmNone, cmScripting, cmHelp, cmBatchSettings, cmKeyGen, cmFingerprintScan, cmDumpCallstack, cmInfo, cmComRegistration,
+  cmCopyId,
 };
 int __fastcall Console(TConsoleMode Mode);
 

+ 4 - 0
source/windows/WinMain.cpp

@@ -928,6 +928,10 @@ int __fastcall Execute()
   {
     Mode = cmKeyGen;
   }
+  else if (Params->FindSwitch(COPYID_SWITCH))
+  {
+    Mode = cmCopyId;
+  }
   else if (Params->FindSwitch(FINGERPRINTSCAN_SWITCH))
   {
     Mode = cmFingerprintScan;