3

Plan

The plan is to disable and subsequently enable a device from inside a windows forms application. To test the first building block of my plan, I open cmd with admin privileges and the following works perfectly:

> devcon hwids =ports
> devcon hwids *VID_10C4*
> devcon disable *VID_10C4*
> devcon enable *VID_10C4*

I can see the device being disabled and enabled again in device manager.

I can also achieve all of this by putting the commands into a batch file and running it from cmd with admin privileges. The above tells me that my plan is essentially good.

Application

However, what I actually want to do is achieve the same thing from inside a windows forms application:

  1. I've set the following in the app manifest:

    requestedExecutionLevel level="requireAdministrator" uiAccess="false"

  2. For the sake of baby steps, I have checked this, just to ensure that there are no stupid mistakes in paths and whatnot. And it works just fine. The log file shows me the expected output from the dir command.

         // Build String 
             string strCmdText =
             "'/c cd " + prodPath +
             " && dir " +
             " > logs\\logFileEnablePrt.txt \"'";
    
    
         // Run command 
             var p = new System.Diagnostics.Process();
             var psi = new ProcessStartInfo("CMD.exe", strCmdText);
             psi.Verb = "runas"; // admin rights
             p.StartInfo = psi;
             p.Start();
             p.WaitForExit();
    
  3. However, this does not work. It always returns an empty log file and does not change the device as expected:

         // Build String 
             string strCmdText =
             "'/c cd " + prodPath +
             " && devcon hwids =ports " +
             " > logs\\logFileEnablePrt.txt \"'";
    
    
         // Run command 
             var p = new System.Diagnostics.Process();
             var psi = new ProcessStartInfo("CMD.exe", strCmdText);
             psi.Verb = "runas"; // admin rights
             p.StartInfo = psi;
             p.Start();
             p.WaitForExit();
    

Error from cmd window is :

'devcon' is not recognized as an internal or external command,

operable program or batch file.

What's going on?

The above has me stumped. I've proved the commands work. I've proved my C# code works. But when I join the 2 together, it doesn't work...

NB: My C# program is running on my D: drive, if that makes any difference...

Updates Based on Comments

@Compo Using your code, it does exactly the same as with mine. I see an empty log file & no changes made to the device. I've altered the /c to /k so I can see what going on the cmd terminal and I see this:

enter image description here

I've even tried your code C:\\Windows\\System32\\devcon hwids =usb pointing directly at devcon. Also tried \devcon.exe for completeness. The inexplicable error is :

enter image description here

I can see the flipping devcon.exe file sitting right there in the folder! Is there any reason it would not recognise it?

Also, with the command as you wrote it, the log file name is actually named logFileEnablePrt.txt'. I agree that your command looks right, so don't ask me why this happens!

enter image description here

@Panagiotis Kanavos using your code, I get the following error:

enter image description here

This is at the line p.Start();. I tried putting in devcon.exe, and even the whole path (I checked the folder was in my PATH, and it is). Can't get past this. I actually stumbled on that answer you shared and arrived at this brick wall already.

monkey
  • 1,213
  • 2
  • 13
  • 35
  • 1
    The C# code doesn't work. Don't use `cmd` in the first place. There are a *lot* of duplicate questions that try to start another program by first starting a shell, then executing the program. When you do that you're awaiting the shell, not the program. The shell though doesn't wait for the program to finish first. Start the application directly instead. Use `"devcon"` or the full path to the program instead of `"cmd.exe"`. – Panagiotis Kanavos May 10 '22 at 06:48
  • To switch to another folder set the Working Directory argument of ProcessStartInfo – Panagiotis Kanavos May 10 '22 at 06:48
  • As for `runas` it doesn't do what you assume. It sends a command to the Windows Shell like `open` or `print` that tells it what to do with the *document* specified in the filename parameter. Windows will use the application registered with `print` or `open` to handle that verb. Your code starts *cmd* though, which doesn't understand verbs. You need to set `UseWindowsShell =true` for verbs to have any effect – Panagiotis Kanavos May 10 '22 at 06:53
  • [This duplicate](https://stackoverflow.com/questions/41767759/processstartinfo-verb-runas-not-working) asks what you want. It tries to start `msiexec.exe` with elevated privileges. – Panagiotis Kanavos May 10 '22 at 06:55
  • I'm going to assume that `devcon hwids =ports`, when passed to `cmd.exe` is separated into arguments. Those arguments are delimited, and one of the standard delimiters as well as the space character, is the equal, `=`, character. What you're probably running therefore is `devcon hwids ports`, and that therefore is your issue. The equal character, `=`, identifies `ports` as a class name, and must therefore be included for your command to work. BTW, the actual command, as resolved, should really be `cmd.exe /c "cd /d "Y:\our\ProdPath" && devcon.exe hwids =ports >"logs\logFileEnablePrt.txt""`. – Compo May 10 '22 at 22:22
  • To expand upon my above comment, perhaps your command shoul be built more like this: `"'/c \"cd /d \"" + prodPath +` `"\" && devcon.exe hwids =ports " +` `">\"logs\\logFileEnablePrt.txt\"\"'";` – Compo May 10 '22 at 22:40
  • @Compo Thanks for comments. See Update in Q. – monkey May 11 '22 at 23:56
  • @PanagiotisKanavos thanks for comments. See updates in Q. – monkey May 11 '22 at 23:57
  • @monkey Is your program 32-bits? – shingo May 12 '22 at 11:16
  • I'd be interested to know what the string path assigned to `prodPath` is @monkey. I say that because your output is specifically using the current directory as `C:\Windows\System32`, which is the default location used as the working directory when 'Run as administrator'. Unless `prodPath` resolves to `C:\Windows\System32`, it is clear to me that the `cd /d` command, _previous to your `devcon` command_, does not change directory as requested. The only way that should happen is if the location does not exist/is not accessible. Are you propagating it with a full absolute path? or a relative path? – Compo May 12 '22 at 22:35
  • @Compo I had a go with this no change... `string prodPath = @"c:\Windows\System32";` – monkey May 12 '22 at 23:32
  • @shingoJust checked. It's 64-bit. Thanks. – monkey May 12 '22 at 23:38
  • 1
    string prodPath = @"c:\Windows\SysNative"; – Hans Passant May 13 '22 at 01:28
  • @HansPassant That's fixed it! Might be worth posting an answer. There are heaps of similar questions that this might solve. And I've got 50 points up for grabs still! – monkey May 13 '22 at 07:54
  • 1
    Your assumption that your code runs in 64-bit is not correct. I can't guess how that went wrong. Make sure you didn't add Platform names, AnyCPU should be the only one for a .NET project. And select the desired bitness on the main project with Project > Properties > Build tab (Platform target = x64, Prefer 32-bit unticked, for both the Debug and Release configurations). – Hans Passant May 13 '22 at 10:44

3 Answers3

2

Here is the code works for me, I don't have ports devices so I change it to usb.

public static void Main()
{
    string prodPath = @"c:\devcon\x64";

    // Build String 
    string strCmdText =
    "/c \"cd /d " + prodPath +
    " && devcon hwids =usb " +
    " > log.txt \"";

    // Run command 
    var p = new Process();
    var psi = new ProcessStartInfo("CMD.exe", strCmdText);
    psi.Verb = "runas"; // admin rights
    p.StartInfo = psi;
    p.Start();
    p.WaitForExit();
}
shingo
  • 18,436
  • 5
  • 23
  • 42
  • Hi. Have edited question to show how I tested this. Pretty sure this is not the issue. – monkey May 09 '22 at 10:40
  • I have some problems with running the code in step 2, the current concatenated command is `cmd '/c cd && dir > logs\\logFileEnablePrt.txt "'`: 1 The `/c` switch is quoted. 2 This switch only recognizes double quote marks. 3 There is only one double quote mark at the end of the string. The correct command is `cmd /c "cd && dir > logs\\logFileEnablePrt.txt"`. Have you changed it? – shingo May 09 '22 at 13:34
  • What I've written works fine for me. But I wouldn't rule out a cut & paste error on my part. If what you've written works for you, awesome, go with that. Can you recreate my problem now by adding in a devon command instead of the dir command (which was just to demo that the thing was working in the first place). – monkey May 09 '22 at 22:49
  • 1
    The problem is caused by using `cmd.exe` instead of the actual program to begin with. This means the actual program can't be awaited. `cmd.exe` doesn't understand Windows Shell verbs either so `runas` has no effect – Panagiotis Kanavos May 10 '22 at 06:56
  • 1
    @PanagiotisKanavos I've ran my code and it successfully outputs my usb devices to the log. `devcon.exe` is a shell program so it is OK to execute it by `cmd.exe` with the `/c` switch. And it is able to understand `runas` verbs if you set `UseShellExecute` to true, but it's not a problem at all because OP said the error is _'The system cannot find the file specified'_ which means this isn't a permission issue. – shingo May 10 '22 at 07:20
1

Worked through a few steps and think I may have an answer...[1]

Just specifying devcon fails as expected...windows cant find the exe as the folder it is in is not in the %PATH% variable in windows..

IF I specify the full path however it works... enter image description here

It wasnt clear from your original code but if your copy of devcon is sitting in either System32 or Syswow directories you may be hitting an emulation issue as well...see here....

EDIT1:: A way to prove this would be to do Direcory.GetFiles(directory containing devcon) and see if the results line up with what you expect

As for passing arguments through to devcon I'd try something like this as opposed to trying to concatenate one giant cmd line..

enter image description here

A similar example but with netstat:

enter image description here

EDIT 2::Another example but with devcon: enter image description here enter image description here enter image description here

The target platform here for the build was x64

EDIT3:: With my application build set to x86:

enter image description here

Shovers_
  • 497
  • 2
  • 13
0

After working through the answers and comments above, I seem to have something that reliably works, which obviously I'd like to share back for scrutiny and future use.

So, my function ended up looking like this:

    private int enablePort(string action)
    {
        while (true)
        {

        // Command Arg
            string devconPath = @"c:\Windows\SysNative";
            string strCmdText =
                "'/c \"cd /d \"" + 
                devconPath +
                "\" && c:\\Windows\\SysNative\\devcon " + action + " *VID_10C4* " +
                "> \"" + prodPath + "\\logs\\logFileEnablePrt.txt\"\"";

        // Process
            var p   = new Process();
            var psi = new ProcessStartInfo()
            {
                Arguments = strCmdText,
                Verb = "runas",
                FileName = "CMD.exe",
                UseShellExecute = true
            };
            p.StartInfo = psi;
            p.Start();
            p.WaitForExit();


                
        // Grab log output
            string logPath = prodPath + "\\logs\\logFileEnablePrt.txt";
            Console.WriteLine("logPath = " + logPath);
            string tempFile = System.IO.File.ReadAllText(logPath);
            System.Console.WriteLine("Contents of WriteText.txt = \n{0}", tempFile);


        // Check if it worked
            var success = false;
            if (tempFile.Contains(action))
            {
                success = true;
                return 0;
            }

            // Error  ->  Allow user to try again!
            if (MessageBox.Show("Was unable to " + action + " Test Jig COM port. Unlug & Replug USB. Check COM port is enabled if not working.", "COM Port Problem", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
            {
                return -1;
            }
        }
    }

And the calling code was:

        this.enablePort("disable");
        int milliseconds = 3000;
        await Task.Delay(milliseconds);
        this.enablePort("enable");

As you can see in the code above, I've logged everything to see what was going on... Stepping through with the debugger, I can now see after the disable:

USB\VID_10C4&PID_EA60\0001 : Disabled 1 device(s) disabled.

And then after the enable:

USB\VID_10C4&PID_EA60\0001 : Enabled 1 device(s) are enabled.

The one extra thing I need to stress is that during testing, I thought I could hook a serial peripheral onto the port and determine whether it could disable and enable successfully by checking the connection. THIS DOES NOT WORK. The above code only works when the port is idle. Perhaps someone who understands the underlying software could hazard an explanation of why this is.

monkey
  • 1,213
  • 2
  • 13
  • 35