【问题标题】:PHP executed program failsPHP执行程序失败
【发布时间】:2015-06-17 09:13:32
【问题描述】:

我想用我的 PHP 后端执行一个 C++ 程序。 C++ 程序负责从我的 PC 中移除 USB 设备,例如 USB 棒。当我在没有管理权限的情况下使用 CLI 打开程序(位于单独的本地驱动器上)时,程序会正确启动并完成工作。

当我使用 exec("/path/to/my/program.exe and-parameters") 使用 PHP 启动程序时,这与从 CLI 启动的方式几乎相同,程序只是启动并返回“失败”,因此使用 CLI 时会有所不同。

C++ 代码:

//
// RemoveDriveByLetter.cpp by Uwe Sieber - www.uwe-sieber.de
//
// Simple demonstration how to prepare a disk drive for save removal
//
// Works with removable and fixed drives under W2K, XP, W2K3, Vista
//
// Console application - expects the drive letter of the drive to remove as parameter
//
// you are free to use this code in your projects
//


#include "stdafx.h"
#include <stdio.h>

#include <windows.h>

#include <Setupapi.h>
#include <winioctl.h>
#include <winioctl.h>
#include <cfgmgr32.h>
#include <string>


//-------------------------------------------------
DEVINST GetDrivesDevInstByDeviceNumber(long DeviceNumber, UINT DriveType, char* szDosDeviceName);
//-------------------------------------------------



//-------------------------------------------------
int main(int argc, char* argv[])
{

    /*if ( argc != 2 ) {
        return 1;       
    }*/
    char DriveLetter = argv[1][0];
    DriveLetter &= ~0x20; // uppercase



    if ( DriveLetter < 'A' || DriveLetter > 'Z' ) {
        return 1;
    }

    std::string path = "";
    path += DriveLetter;
    path.append(":\\");
    printf(path.c_str());

    char szRootPath[sizeof(path)] ="";
    strncpy(szRootPath, path.c_str(), sizeof(path));

    std::string device = "";
    device += DriveLetter;
    device.append(":");
    printf(device.c_str());

    char szDevicePath[sizeof(device)] = "";
    strncpy(szDevicePath, device.c_str(), sizeof(device));

    std::string accesspath = "";
    accesspath += "\\\\.\\";
    accesspath += device;
    printf(accesspath.c_str());

    char szVolumeAccessPath[sizeof(accesspath)] = "";   // "\\.\X:"  -> to open the volume
    strncpy(szVolumeAccessPath, accesspath.c_str(), sizeof(accesspath));

    long DeviceNumber = -1;

    // open the storage volume
    HANDLE hVolume = CreateFile(szVolumeAccessPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
    if (hVolume == INVALID_HANDLE_VALUE) {
        return 1;
    }

    // get the volume's device number
    STORAGE_DEVICE_NUMBER sdn;
    DWORD dwBytesReturned = 0;
    long res = DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL);
    if ( res ) {
        DeviceNumber = sdn.DeviceNumber;
    }
    CloseHandle(hVolume);

    if ( DeviceNumber == -1 ) {
        return 1;
    }

    // get the drive type which is required to match the device numbers correctely
    UINT DriveType = GetDriveType(szRootPath);

    // get the dos device name (like \device\floppy0) to decide if it's a floppy or not - who knows a better way?
    char szDosDeviceName[MAX_PATH];
    res = QueryDosDevice(szDevicePath, szDosDeviceName, MAX_PATH);
    if ( !res ) {
        return 1;
    }

    // get the device instance handle of the storage volume by means of a SetupDi enum and matching the device number
    DEVINST DevInst = GetDrivesDevInstByDeviceNumber(DeviceNumber, DriveType, szDosDeviceName);

    if ( DevInst == 0 ) {
        return 1;
    }

    PNP_VETO_TYPE VetoType = PNP_VetoTypeUnknown; 
    WCHAR VetoNameW[MAX_PATH];
    VetoNameW[0] = 0;
    bool bSuccess = false;

    // get drives's parent, e.g. the USB bridge, the SATA port, an IDE channel with two drives!
    DEVINST DevInstParent = 0;
    res = CM_Get_Parent(&DevInstParent, DevInst, 0); 

    for ( long tries=1; tries<=3; tries++ ) { // sometimes we need some tries...

        VetoNameW[0] = 0;

        // CM_Query_And_Remove_SubTree doesn't work for restricted users
        //res = CM_Query_And_Remove_SubTreeW(DevInstParent, &VetoType, VetoNameW, MAX_PATH, CM_REMOVE_NO_RESTART); // CM_Query_And_Remove_SubTreeA is not implemented under W2K!
        //res = CM_Query_And_Remove_SubTreeW(DevInstParent, NULL, NULL, 0, CM_REMOVE_NO_RESTART);  // with messagebox (W2K, Vista) or balloon (XP)

        res = CM_Request_Device_EjectW(DevInstParent, &VetoType, VetoNameW, MAX_PATH, 0);
        //res = CM_Request_Device_EjectW(DevInstParent, NULL, NULL, 0, 0); // with messagebox (W2K, Vista) or balloon (XP)

        bSuccess = (res==CR_SUCCESS && VetoType==PNP_VetoTypeUnknown);
        if ( bSuccess )  { 
            break;
        }

        Sleep(500); // required to give the next tries a chance!
    }

    if ( bSuccess ) {
        printf("Success\n\n");
        return 0;
    }

    printf("failed\n");

    printf("Result=0x%2X\n", res);

    if ( VetoNameW[0] ) {
        printf("VetoName=%ws)\n\n", VetoNameW);
    }   
    return 1;
}
//-----------------------------------------------------------

char* appendCharToCharArray(char* array, char a)
{
    size_t len = strlen(array);

    char* ret = new char[len+2];

    strcpy(ret, array);    
    ret[len] = a;
    ret[len+1] = '\0';

    return ret;
}


//----------------------------------------------------------------------
// returns the device instance handle of a storage volume or 0 on error
//----------------------------------------------------------------------
DEVINST GetDrivesDevInstByDeviceNumber(long DeviceNumber, UINT DriveType, char* szDosDeviceName)
{
    bool IsFloppy = (strstr(szDosDeviceName, "\\Floppy") != NULL); // who knows a better way?

    GUID* guid;

    switch (DriveType) {
    case DRIVE_REMOVABLE:
        if ( IsFloppy ) {
            guid = (GUID*)&GUID_DEVINTERFACE_FLOPPY;
        } else {
            guid = (GUID*)&GUID_DEVINTERFACE_DISK;
        }
        break;
    case DRIVE_FIXED:
        guid = (GUID*)&GUID_DEVINTERFACE_DISK;
        break;
    case DRIVE_CDROM:
        guid = (GUID*)&GUID_DEVINTERFACE_CDROM;
        break;
    default:
        return 0;
    }

    // Get device interface info set handle for all devices attached to system
    HDEVINFO hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);

    if (hDevInfo == INVALID_HANDLE_VALUE)   {
        return 0;
    }

    // Retrieve a context structure for a device interface of a device information set
    DWORD dwIndex = 0;
    long res;

    BYTE Buf[1024];
    PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf;
    SP_DEVICE_INTERFACE_DATA         spdid;
    SP_DEVINFO_DATA                  spdd;
    DWORD                            dwSize;

    spdid.cbSize = sizeof(spdid);

    while ( true )  {
        res = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, guid, dwIndex, &spdid);
        if ( !res ) {
            break;
        }

        dwSize = 0;
        SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, NULL, 0, &dwSize, NULL); // check the buffer size

        if ( dwSize!=0 && dwSize<=sizeof(Buf) ) {

            pspdidd->cbSize = sizeof(*pspdidd); // 5 Bytes!

            ZeroMemory(&spdd, sizeof(spdd));
            spdd.cbSize = sizeof(spdd);

            long res = SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, pspdidd, dwSize, &dwSize, &spdd);
            if ( res ) {

                // in case you are interested in the USB serial number:
                // the device id string contains the serial number if the device has one,
                // otherwise a generated id that contains the '&' char...
                /*
                DEVINST DevInstParent = 0;
                CM_Get_Parent(&DevInstParent, spdd.DevInst, 0); 
                char szDeviceIdString[MAX_PATH];
                CM_Get_Device_ID(DevInstParent, szDeviceIdString, MAX_PATH, 0);
                printf("DeviceId=%s\n", szDeviceIdString);
                */

                // open the disk or cdrom or floppy
                HANDLE hDrive = CreateFile(pspdidd->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
                if ( hDrive != INVALID_HANDLE_VALUE ) {
                    // get its device number
                    STORAGE_DEVICE_NUMBER sdn;
                    DWORD dwBytesReturned = 0;
                    res = DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL);
                    if ( res ) {
                        if ( DeviceNumber == (long)sdn.DeviceNumber ) {  // match the given device number with the one of the current device
                            CloseHandle(hDrive);
                            SetupDiDestroyDeviceInfoList(hDevInfo);
                            return spdd.DevInst;
                        }
                    }
                    CloseHandle(hDrive);
                }
            }
        }
        dwIndex++;
    }

    SetupDiDestroyDeviceInfoList(hDevInfo);

    return 0;
}
//-----------------------------------------------------------

程序返回:

array(2) ( [0] => (string) D:\D:\.\D:failed [1] => (string) 结果=0x33)

有人建议吗?

【问题讨论】:

  • 因为exec 函数使用与网络服务器相同的用户运行请求的程序?可能无权做你想做的事。
  • 只显示这个 exe 文件的ls -l 加上你应该从 exec 获得更多反馈显示所有错误等等
  • @Snickbrack,Joachim 是对的,您的经过身份验证的用户可能拥有与系统相同的权限,但很可能 Web 服务器用户没有。试试 ps aux 看看哪个用户在运行你的网络服务器。
  • 看起来我们需要更多信息。你的结果和否决类型的值是什么?
  • 您需要提供的第一件事是您的可执行文件在哪一行中止并声称它已失败。可能存在权限问题.. 但要确保您需要找出代码实际运行到哪一行。这意味着一些日志记录会很方便。

标签: php c++ windows iis


【解决方案1】:

首先通过php client(从shell)运行命令。

<?php
exec("/path/to/my/program.exe and-parameters");

.

$ php.exe -f file.php //Check link above. I am used to Linux and you might need diffrent params

如果可行,那么很可能是 iis 权限问题。

【讨论】:

  • 会不会是PHP/IIS启动的程序无权更改/编辑另一个驱动器?
  • 我是 Linux 人,所以我不知道。但是你可以在这里问这个问题:serverfault.com/questions/tagged/iis
  • 您使用注册表项的解决方案对我不起作用,因为我不知道如何设置这些必需的键
  • 你想说你自己的链接的内容还没有看过吗?我的意思是 php.net-article 中提到的注册表项
  • 我是 Linux 人!但是,我过去确实使用过 PHP 和 Windows,但我不记得在 Windows 注册表中配置 PHP 所需的任何操作。如果需要,请阅读手册。建议的测试旨在测试 PHP 在 IIS 之外运行脚本的能力。 Windows 调优问题应该在 ServerFault 上
【解决方案2】:

如果您在安全模式下运行 PHP,则仅允许运行 safe_mode_exec_dir 中的文件。

您似乎在 Windows 环境中运行。您可能需要考虑使用 Windows shell 执行此操作,这使您可以更好地控制外部执行程序,并且在失败时可能会返回其他信息并帮助诊断 exec() 的潜在问题功能。

来自在线PHP手册的评论是:

在后台最小化启动Notepad.exe:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("notepad.exe", 7, false); 
?> 

启动一个后台不可见的shell命令:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("cmd /C dir /S %windir%", 0, false); 
?> 

最大化启动 MSPaint 并等待您将其关闭,然后再继续执行脚本:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("mspaint.exe", 3, true); 
?> 

有关 Run() 方法的更多信息,请访问: https://msdn.microsoft.com/en-us/subscriptions/d5fk67ky(v=vs.84).aspx

【讨论】:

  • 这个解决方案对我不起作用,COM-Line 上的 PHP 崩溃
  • 它仍然不起作用,但我不想浪费赏金。
  • 您是在 Windows 下运行吗?如果是什么味道?你得到什么错误?试试这个来安装 WSH:microsoft.com/en-us/download/details.aspx?id=8247
【解决方案3】:

This article 描述了如何设置应用程序池并将其与您的 PHP 服务相关联。完成后,右键单击您创建的应用程序池并选择“高级设置”。在“流程模型”标题下,您将看到一个名为“身份”的设置;将值更改为具有您寻求的权限的帐户(例如“本地系统”或“本地服务”)。

默认应用程序池的标识(您的 PHP 服务当前在其下运行)对于您的目的来说不够强大!

【讨论】:

    猜你喜欢
    • 2019-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多