1

We have a legacy Delphi 7 application that launches the Windows Defrag and On-screen Keyboard applications as follows:

// Defragmentation application
ShellExecute(0, 'open', PChar('C:\Windows\System32\dfrg.msc'), nil, nil, SW_SHOWNORMAL);

// On-screen keyboard
ShellExecute(0, 'open', PChar('C:\Windows\System32\osk.exe'), nil, nil, SW_SHOWNORMAL);

Both work on Windows XP but fail on Windows 10. I spotted that the defragmentation application has had a name change to dfrgui.exe, but updating the code does not help. The On-screen Keyboard is still called osk.exe on Windows 10.

Both applications can be launched manually / directly from the command line or by double-clicking them in Windows Explorer.

My suspicion is that Windows security is preventing my application from launching anything from C:\Windows\System32, because I can launch several other applications from Program Files and from C:\Windows.

Can anyone help?

AlainD
  • 5,413
  • 6
  • 45
  • 99
  • You should be using `ShellExecuteEx()` instead of `ShellExecute()`, the `Ex` version provides better error reporting. And you should be using `CreateProcess()` instead of `ShellExecute/Ex()` when running EXE files in particular. None of this addresses your issue, it is just good programing practice to follow in general. – Remy Lebeau Jun 22 '18 at 18:31
  • 2
    Hmm, turns out you [can't use `CreateProcess()` to run `osk.exe`](https://stackoverflow.com/questions/23116149/), though. It must be run with `ShellExecute/Ex()`. – Remy Lebeau Jun 22 '18 at 21:02

2 Answers2

8

Delphi 7 produces only 32-bit apps, there is no option to produce 64-bit apps (that was added in XE2).

Accessing a path under %WINDIR%\System32 from a 32-bit app running on a 64-bit system is subject to WOW64's File System Redirector, which will silently redirect requests for the 64-bit System32 folder to the 32-bit SysWOW64 folder instead.

Chances are, the apps you are trying to run only exist in the 64-bit System32 folder and not in the 32-bit SysWOW64 folder.

To avoid redirection, you need to either:

  • replace System32 with the special Sysnative alias in your paths (ie 'C:\Windows\Sysnative\osk.exe'), which only works when running under WOW64, so you have to detect that dynamically at runtime via IsWow64Process():

    function GetSystem32Folder: string;
    var
      Folder: array[0..MAX_PATH] of Char;
      IsWow64: BOOL;
    begin
      Result := '';
      if IsWow64Process(GetCurrentProcess(), @IsWow64) and IsWow64 then
      begin
        SetString(Result, Folder, GetWindowsDirectory(Folder, Length(Folder)));
        if Result <> '' then
          Result := IncludeTrailingPathDelimiter(Result) + 'Sysnative' + PathDelim;
      end else
      begin
        SetString(Result, Folder, GetSystemDirectory(Folder, Length(Folder)));
        if Result <> '' then
          Result := IncludeTrailingPathDelimiter(Result);
      end;
    end;
    
    function RunDefrag: Boolean;
    var
      SysFolder: string;
      Res: Integer;
    begin
      SysFolder := GetSystem32Folder;
      Res := Integer(ShellExecute(0, nil, PChar(SysFolder + 'dfrgui.exe'), nil, nil, SW_SHOWNORMAL));
      if Res = ERROR_FILE_NOT_FOUND then
        Res := Integer(ShellExecute(0, nil, PChar(SysFolder + 'dfrg.msc'), nil, nil, SW_SHOWNORMAL));
      Result := (Res = 0);
    end;
    
    function RunOnScreenKeyboard: Boolean;
    begin
      Result := (ShellExecute(0, nil, PChar(GetSystem32Folder + 'osk.exe'), nil, nil, SW_SHOWNORMAL) = 0);
    end;
    
  • temporarily disable the Redirector via Wow64DisableWow64FsRedirection(), and then re-enable it via Wow64RevertWow64FsRedirection() when done:

    function GetSystem32Folder: string
    var
      Folder: array[0..MAX_PATH] of Char;
    begin
      SetString(Result, Folder, GetSystemDirectory(Folder, Length(Folder)));
      if Result <> '' then
        Result := IncludeTrailingPathDelimiter(Result);
    end;
    
    function RunDefrag: Boolean;
    var
      SysFolder: string;
      OldState: Pointer;
      Res: Integer;
    begin    
      Wow64DisableWow64FsRedirection(@OldState);
      try
        SysFolder := GetSystem32Folder;
        Res := Integer(ShellExecute(0, nil, PChar(SysFolder + 'dfrgui.exe'), nil, nil, SW_SHOWNORMAL));
        if Res = ERROR_FILE_NOT_FOUND then
          Res := Integer(ShellExecute(0, nil, PChar(SysFolder + 'dfrg.msc'), nil, nil, SW_SHOWNORMAL));
        Result := Res = 0;
      finally
        Wow64RevertWow64FsRedirection(OldState);
      end;
    end;
    
    function RunOnScreenKeyboard: Boolean;
    var
      OldState: Pointer;
    begin
      Wow64DisableWow64FsRedirection(@OldState);
      try
        Result := (ShellExecute(0, nil, PChar(GetSystem32Folder + 'osk.exe'), nil, nil, SW_SHOWNORMAL) = 0);
      finally
        Wow64RevertWow64FsRedirection(OldState);
      end;
    end;
    

Update: that being said, it turns out that a 32-bit process running under WOW64 is not allowed to run osk.exe when UAC is enabled:

Delphi - On Screen Keyboard (osk.exe) works on Win32 but fails on Win64

So, you will have to create a helper 64-bit process to launch osk.exe on your app's behalf when it is running under WOW64.

Daniel Marschall
  • 3,739
  • 2
  • 28
  • 67
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
1

A small addition to Remy Lebeau's answer:

If Wow64DisableWow64FsRedirection is not available in your Delphi version, and/or if you are not sure if your target platform will support this API, you could use following code sample that calls the function dynamically:

https://www.delphipraxis.net/155861-windows-7-64bit-redirection.html

function ChangeFSRedirection(bDisable: Boolean): Boolean;
type
     TWow64DisableWow64FsRedirection = Function(Var Wow64FsEnableRedirection: LongBool): LongBool; StdCall;
     TWow64EnableWow64FsRedirection = Function(var Wow64FsEnableRedirection: LongBool): LongBool; StdCall;
var
    hHandle: THandle;
    Wow64DisableWow64FsRedirection: TWow64DisableWow64FsRedirection;
    Wow64EnableWow64FsRedirection: TWow64EnableWow64FsRedirection;
    Wow64FsEnableRedirection: LongBool;
begin
  Result := false;

  try
    hHandle := GetModuleHandle('kernel32.dll');
    @Wow64EnableWow64FsRedirection := GetProcAddress(hHandle, 'Wow64EnableWow64FsRedirection');
    @Wow64DisableWow64FsRedirection := GetProcAddress(hHandle, 'Wow64DisableWow64FsRedirection');

    if bDisable then
    begin
     if (hHandle <> 0) and (@Wow64DisableWow64FsRedirection <> nil) then
     begin
       Result := Wow64DisableWow64FsRedirection(Wow64FsEnableRedirection);
     end;
    end else
    begin
     if (hHandle <> 0) and (@Wow64EnableWow64FsRedirection <> nil) then
     begin
       Result := Wow64EnableWow64FsRedirection(Wow64FsEnableRedirection);
       Result := True;
     end;
    end;
  Except
  end;
end; 
Daniel Marschall
  • 3,739
  • 2
  • 28
  • 67