0

I am building a Windows Service. It needs to run a command using current user privileges and delete all stored credentials which matches a substring - e.g. "MYSTRING".

I have invoked other modules in CreateProcessAsUser method which does work. However, invoking a cmd.exe module and passing complex command seems to be tricky.

I am probably missing some escape characters or some additional command line arguments, causing complex command to fail to run successfully. If I invoke my service I can see cmd prompt starting, but it does not execute the command.

I did try to troubleshoot it. I tested it in the command cmd prompt directly, which works.

Here are the examples:

Directly from cmd prompt:

FOR /F "usebackq tokens=1* delims=: " %i in (`cmdkey /list^|findstr MYSTRING`) do cmdkey /delete:%j

Same command fails when executed via the run box:

cmd.exe /K FOR /F "usebackq tokens=1* delims=: " %%i in (`cmdkey /list^|findstr MYSTRING`) do cmdkey /delete:%%j

I tried adding double quotes, escaping special characters, but somehow it is not working. Below are such examples.

cmd.exe /K "FOR /F ^"usebackq tokens=1* delims=: ^" %%i in (`cmdkey /list^|findstr ADAL`) do cmdkey /delete %%j"

cmd.exe /K "FOR /F ^"usebackq tokens=1* delims^=: ^" %%i in (^`cmdkey /list^|findstr ADAL^`) do cmdkey /delete %%j"

Thanks in advance for your help.

Edit:

Based on tukan's input I was able to run the command from run box. However, when I try the same in the C# code, it is throwing error in the command prompt.

Error in command prompt (process created by service):

tokens = 1 * delims =: " was unexpected at this time.

Below is the C# method.

public static bool ClearCredsCache()
        {
            var hUserToken = IntPtr.Zero;
            var startInfo = new STARTUPINFO();
            var procInfo = new PROCESS_INFORMATION();
            var pEnv = IntPtr.Zero;
            int iResultOfCreateProcessAsUser;
            //String cmdLine = "cmd.exe \"FOR / F \"usebackq tokens=1* delims=: \" %%i in (`cmdkey / list ^| findstr MYSTRING`) do cmdkey / list %%j\"& pause";
            String cmdLine = "cmd.exe /K FOR /F \"usebackq tokens = 1 * delims =: \" %i in (`cmdkey /list^|findstr MYSTRING`) do cmdkey /delete:%j";
            //String cmdLine = null;
            String appPath = "cmd.exe";
            String workDir = null;
            bool visible = true;

            startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO));

            try
            {
                if (!GetSessionUserToken(ref hUserToken))
                {
                    throw new CredMgmtException(CredMgmtException.medium, "StartProcessAsCurrentUser: GetSessionUserToken failed.");
                }

                uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW);
                startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE);
                startInfo.lpDesktop = null;

                if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false))
                {
                    throw new CredMgmtException(CredMgmtException.medium, "StartProcessAsCurrentUser: CreateEnvironmentBlock failed.");
                }

                if (!CreateProcessAsUser(hUserToken,
                    appPath, // Application Name
                    cmdLine, // Command Line
                    IntPtr.Zero,
                    IntPtr.Zero,
                    false,
                    dwCreationFlags,
                    pEnv,
                    workDir, // Working directory
                    ref startInfo,
                    out procInfo))
                {
                    iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
                    throw new CredMgmtException(CredMgmtException.medium, "StartProcessAsCurrentUser: CreateProcessAsUser failed.  Error Code -" + iResultOfCreateProcessAsUser);
                }

                iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
            }
            finally
            {
                CloseHandle(hUserToken);
                if (pEnv != IntPtr.Zero)
                {
                    DestroyEnvironmentBlock(pEnv);
                }
                CloseHandle(procInfo.hThread);
                CloseHandle(procInfo.hProcess);
            }
           //stdOut.
           // CredMgmtUtil.WriteEvent("Startupinfo=" + startInfo.hStdOutput)
            return true;
        }
Indranil
  • 1,776
  • 1
  • 17
  • 22
  • Why did you double `%` in the second command? – user4003407 Dec 14 '17 at 07:38
  • not clear, what you want. provide some c# code, if it really is a c# question. You might need the @ operator ```string mystring = @"String, which doesnt resolve escapesequences, C:\stuff stays C:\stuff";``` – FrankM Dec 14 '17 at 07:38
  • @PetSerAl Yes I added %%. Command: cmd.exe /K FOR /F "usebackq tokens=1* delims=: " %%i in (`cmdkey /list^|findstr MYSTRING`) do cmdkey /delete:%%j – Indranil Dec 14 '17 at 07:39
  • @FrankM Sure I can add C# code. But I felt, since the command itself doesn't work from run box, there is no point debugging the C# code. Please suggest. – Indranil Dec 14 '17 at 07:41
  • Please add the C# code. Add all you tried to escape to your example too. – tukan Dec 14 '17 at 07:42
  • Ok, thx for clarification, so its not a c# issue, as your c# Tag says – FrankM Dec 14 '17 at 07:43
  • @FrankM I have added the C# code, please check. – Indranil Dec 14 '17 at 08:28
  • 2
    Thx :) your copy of tukans cmdline has more whitespaces, which could cause errors: (yours)```tokens = 1 * delims =: ``` vs (tukans)```tokens=1* delims=: ``` visual studio does this(adding whitespaces on paste) some times – FrankM Dec 14 '17 at 08:37
  • @FrankM you run me to it :). – tukan Dec 14 '17 at 08:48

1 Answers1

1

I did test it from the run box and it executes well without errors on Windows 7 x64.

cmd.exe /K FOR /F "usebackq tokens=1* delims=: " %i in (`cmdkey /list^|findstr tukan`) do echo "test"

Both ways (cmd.exe & run box) produce correctly two echo lines.

What windows do you have? If you execute in the cmd.exe just cmdkey /list^|findstr ADAL what do you get?

tukan
  • 17,050
  • 1
  • 20
  • 48
  • I am using Windows 7 – Indranil Dec 14 '17 at 07:56
  • @Indranil: so that line will work correctly as I have windows 7 too. Please run the test with `echo` command before performing any actual *action*. – tukan Dec 14 '17 at 07:57
  • I tried the full command, its working. Double % was the issue. Next I will try from the C# service method and confirm. – Indranil Dec 14 '17 at 08:00
  • 4
    You only need to double the `%` when the `FOR` command is in a batch file. – Michael Burr Dec 14 '17 at 08:01
  • @Indranil: I can imagine that with service you may have some rights issues. You may try to run it as `START "runas /user:administrator" cmd /K ...` – tukan Dec 14 '17 at 08:07
  • @tukan Not sure, if I can get hold of current user's credential cache, if I use elevated privilege. So far, i didn't see rights issue, once command runs I will get to know, I have edited the question and added the C# code. Now I am getting `tokens = 1 * delims =: " was unexpected at this time.` in the command prompt created by the service. – Indranil Dec 14 '17 at 08:30
  • @Indranil I don't know if the stdout is the cause here, but you have more whitespaces there. – tukan Dec 14 '17 at 08:38
  • @tukan @FrankM @Michael Burr I have modified the command from C# service as below and it works!! `String cmdLine = "cmd.exe /K FOR /F \"usebackq tokens=1* delims=: \" %i in (`cmdkey /list^|findstr ADAL`) do cmdkey /delete:%j";`. Thanks everyone for your help!!. However, I would like get advise from you, if I am doing the right thing. My requirement is to delete stored credential for the logged in user from a windows service. Please let me know, if above approach (the code snippet in the question) is correct way clearing user's stored credential from a service. – Indranil Dec 14 '17 at 16:05
  • @Indranil if it works for why not. I personally would use only powershell, but that is matter of personal preference. – tukan Dec 14 '17 at 18:30
  • @tukan Is there any way I can add one more command after the for loop. I am trying with below command (added exit command), so that cmd prompt exits after the for loop ends. However, for loop is not executing properly when tried from run box and exits even before for loop is executed. `cmd.exe /K FOR /F "usebackq tokens=1* delims=: " %i in (`cmdkey /list^|findstr INVALID`) do (cmdkey /delete:%j) & exit` – Indranil Dec 14 '17 at 20:31
  • 1
    @Indranil If you wish for the command prompt (cmd.exe) to exist after execution you need to exchange the switch in the `cmd.exe /K` for `cmd.exe /C`. You don't need any extra command(s) at the end. – tukan Dec 15 '17 at 08:26
  • Thanks @tukan for the help. Made the change. I can see conhost process is getting killed properly. – Indranil Dec 15 '17 at 16:15