4

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.
eyeClaxton
  • 480
  • 4
  • 15
  • try using the `Process Monitor` http://technet.microsoft.com/en-us/sysinternals/bb896645 , with this tool you can inspect the file system activity for any process. – RRUZ Jul 08 '11 at 21:17
  • 2
    I doubt that ShellExecuteEx is trying to write to the drive. How is the second program accessing the file? Are you sure the second program is even launching? – Peter Ruderman Jul 08 '11 at 21:17
  • @Peter the 2nd program source code is provided (project1.dpr). It does nothing but display a message box. The issue is that the 2nd program is not even launching, since ShellExecute returns an error. – Arnaud Bouchez Jul 09 '11 at 07:40
  • Seems to be an identified restriction, which appeared with XP SP3: it's typically a security feature introduced with a Windows Update. See http://www.powerbasic.com/support/pbforums/showthread.php?t=37689 – Arnaud Bouchez Jul 09 '11 at 07:41
  • @A.Bouchez your comment is right on, I just tested on Vista and Windows 7 and it works as expected, but on Windows XP SP3 it doesn't. – eyeClaxton Jul 09 '11 at 08:34

1 Answers1

6

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.
Community
  • 1
  • 1
Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159
  • I don't need project1.exe to run elevated on Windows XP, I'll test for OS version and then call the WinExec API, if XP is detected. Tested and works! Thanks much for your help. – eyeClaxton Jul 09 '11 at 09:10