Question

I'm trying to test an application on a write protected USB drive, I want to use the ShellExecuteEx API (I need to use this API call because I need the lpVerb := "runas") call to execute a second program, but I keep getting a "Write Protect Error" with the ShellExecuteEx call. I can’t seem to figure out what’s trying to write to the drive, I have no code that is writing to the drive and I even used the latest Microsoft Standard User Analyzer and Application Verifier to try and verify what is trying to write to the drive with no success. Here is the error I keep getting:

[ Write Protect Error ]

Write Protect Error

Nothing in the following code is trying to write to this drive, is the ShellExecuteEx API call the wrong way to do what I'm trying to do? If not, how can I get this error from popping up. Any help would be greatly appreciated.

[ WP-ON.reg ]

REGEDIT4

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies]
"WriteProtect"=dword:00000001

[ WP-OFF.reg ]

REGEDIT4

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies]
"WriteProtect"=dword:00000000

NOTE: You must eject and reinsert the device each time the registry has been updated.

[ project1.dpr ]

program project1;

{.$APPTYPE CONSOLE}

uses
  Windows, SysUtils;

begin
  Windows.MessageBox(Windows.GetActiveWindow(),
    PChar('Hello World!'), PChar('project1'), MB_OK);
end.

[ launch.manifest ]

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity processorArchitecture="x86" version="2.0.1.0" name="eyeClaxton.asInvoker.Launch" type="win32" />
<description>asInvoker Launch</description>
<dependency>
    <dependentAssembly>
        <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" publicKeyToken="6595b64144ccf1df" language="*" processorArchitecture="x86" />
    </dependentAssembly>
</dependency>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
        <requestedPrivileges>
            <requestedExecutionLevel level="asInvoker" uiAccess="false" />
        </requestedPrivileges>
    </security>
</trustInfo>
</assembly>

[ launch.dpr ]

program launch;

uses
  Windows, ShellAPI;

{$R 'MANIFEST.RES'}

procedure ShellEx(const theFilename, theParams: string);

  function RunAsAdmin(): Boolean;
  var
    OSVerInfo: TOSVersionInfo;
  begin
    OSVerInfo.dwOSVersionInfoSize := System.SizeOf(OSVerInfo);
    Result := (Windows.GetVersionEx(OSVerInfo)) and (OSVerInfo.dwMajorVersion > 5);
  end;

var
  ShellExInfo: TShellExecuteInfo;
  Directory: array[0..MAX_PATH] of Char;
begin
  Windows.ZeroMemory(@ShellExInfo, System.SizeOf(ShellExInfo));
  ShellExInfo.cbSize := System.SizeOf(TShellExecuteInfo);
  ShellExInfo.Wnd := Windows.GetActiveWindow();
  ShellExInfo.nShow := SW_SHOWNORMAL;
  ShellExInfo.fMask := SEE_MASK_FLAG_NO_UI;

  if (RunAsAdmin()) then  // If OS is greater than Windows XP
    ShellExInfo.lpVerb := PChar('runas');

  Windows.ZeroMemory(@Directory, System.SizeOf(Directory));
  Windows.GetCurrentDirectory(SizeOf(Directory), Directory);
  ShellExInfo.lpDirectory := PChar(string(Directory));
  ShellExInfo.lpFile := PChar('"' + string(Directory) + '\' + theFilename + '"');
  ShellExInfo.lpParameters := PChar('"' + theParams + '"');

  //
  // ShellExecuteEx causes a "Write Protect" error to popup.
  //
  if (not ShellAPI.ShellExecuteEx(@ShellExInfo)) then
    Windows.MessageBox(Windows.GetActiveWindow(),
      PChar('File ' + ShellExInfo.lpFile + ' not found!'),
      PChar('asInvoker Launch'), MB_OK or MB_ICONERROR);
end;

begin
  ShellEx('project1.exe', System.ParamStr(1));

end.
Was it helpful?

Solution

The issue comes from the fact ShellExecuteEx is quite a complex process, starting a new background thread, then calling a lot of COM stuff. Write should be enabled somewhere, for security reasons.

You may try to disable the UAC. But not a good solution.

When you look at the corresponding solutions to elevate a process rights programatically (wired manifest is another static way of elevation), you can only find the two quoted in this SO answer:

  • Use ShellExecuteEx with runas parameter;
  • Create an elevated COM Object with the magic "Elevation:Administrator!new:" prefix.

It's worth reading the whole "Vista UAC: The Definitive Guide" article to understand how ShellExecuteEx works.

So here is my answer: since you need to run a process with elevated rights, and there is no existing "CreateProcessElevated" API but only the good big ShellExecute, the most easy is to use a manifest file for the 2nd executable.

If you want your project1.exe file to run with "AsInvoker" rights, but only with administrator rights on purpose, you have two possibilities:

  • Use an external .manifest file, and do not embed the file to the exe as a resource - then replace the .manifest content with one version with either "AsInvoker" or "requireAdministrator" parameter - but difficult on a read-only media, isn't it :;
  • Use an external 3nd executable (we'll call it Elevate.exe), containing a manifest with the "requireAdministrator" level, which will launch the 2nd executable. You could provide the exe and command line as a parameter to this Elevate.exe program.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top