A short while ago, I came out with a small script to properly configure PHP for IIS7. Sure enough, I got asked to make a similar one for IIS6. So, I figure that while I am at it, I might as well cover IIS4, IIS5, and IIS5.1 as well since it is not very different, and put it all into one script so that you can see one classic way to maintain a single automation script which runs version-specific logic.

For the astute reader - no, this script is not minimal, optimal, nor foolproof... it is illustrative. I am deliberately showing several possibilities at the expense of conciseness... but I hope you agree that the information is worth more than the result here...

To correctly configure the PHP Application Mapping prior to IIS7, you MUST provide a tool that can modify the IIS LIST data type.

I have provided such a tool in this blog entry, so you need to copy that script tool into the same directory as you copy this script and name it "ChgList.vbs". If you want to put the tool in a different directory or with a different name, you must modify the FILE_CHGLIST variable in this script appropriately to give the complete pathname.

In addition, I made several little illustrative enhancements:

  • Debug mode - if you want to merely SEE what is going to execute but NOT execute anything, set the _DEBUG environment variable to 1. Default executes.
  • Functions vs. Labels - the label :VerifyScripts is treated as a FUNCTION in batch (with ERRORLEVEL as the return value), while the labels :Menu and :Start are treated like GOTO labels
  • File Existence Validations - depending on the OS/IIS Version, validate the existence of necessary files and scripts
  • OS BuildNumber detection

Yes, when you write scripts/tools meant to run on multiple platforms and versions, you get constrained into the most reliable least-common-denominator and never get to use the new-fangled stuff. But that's the difference between getting stuff done with compatible software vs experimenting with the bleeding edge... ;-)

In this case, I am using OS BuildNumber, parsed from 'ver', to determine IIS version. If I can constrain this script to IIS5.1 and above (or W2K with REG.EXE from the Resource Kit), I can use REG.EXE to read the installed IIS version from the Registry... but I am not making those assumptions and hence use the OS BuildNumber as a compatible mechanism.

And to be complete - all of these actions require you to run with administrative privileges since you cannot modify the IIS Configuration file(s) without them. Prior to Vista, this means the user must be in the local Administrators group. On Vista with UAC (default), it means that you either run as the built-in Administrator (disabled by default) or run the script with elevated permissions (by saving the script and right-click running as Administrator).

One final disclaimer:

*** Please realize that the script tool simply makes PHP work in one configuration (default). It is not meant to fix or make your arbitrary configuration work ***

In particular, if you run this script more than once, it may not work correctly or configure duplicate settings. For example, APPCMD will fail to configure duplicate handlers and WebServiceExtension entries, iisext.vbs will fail to configure duplicate WebServiceExtension entries, and ChgList.vbs will keep adding duplicate .php ScriptMappings. It is a slippery slope, so I draw the line early.

Also, the script does not go through your ScriptMaps to change the right ones; you can do that yourself. It also does not verify file ACLs, user identities and permissions, etc - it assumes everything is working perfectly and you just need to make the minimal IIS-related configuration to make PHP work.

Sorry... but please understand that I am not in the business of writing and supporting installation programs for other products nor troubleshooting why it does not work on IIS. I am just trying to show how things work, together.

Enjoy.

//David

@IF ?%_ECHO%?==?? ECHO OFF

SETLOCAL
IF ?%_DEBUG%? EQU ?1? (SET DEBUG=ECHO) ELSE (SET DEBUG=)
SET FILE_CHGLIST=chglist.vbs
SET FILE_IISEXT=%SYSTEMROOT%\System32\iisext.vbs
SET CMD_CHGLIST=CSCRIPT //NoLogo %FILE_CHGLIST%
SET CMD_IISEXT=CSCRIPT //NoLogo %FILE_IISEXT%
SET CMD_APPCMD=%SYSTEMROOT%\System32\inetsrv\APPCMD.EXE
SET DIR_PHP_FROM=%SYSTEMDRIVE%\Inetpub\PHP
SET PHP_TYPE=ISAPI
SET PHP_MODULE=IsapiModule
SET PHP_BINARY=php5isapi.dll

REM
REM Determine OS BuildNumber
REM 1381    NT4     IIS4
REM 2195    W2K     IIS5
REM 2600    WXP     IIS5.1
REM 3790    WS03    IIS6
REM Other   Vista   IIS7
REM
FOR /f "tokens=3 delims=.]" %%i IN ('ver') DO SET OS_BUILDNUMBER=%%i
IF "%OS_BUILDNUMBER%"=="" FOR /f "tokens=4" %%i IN ('ver') DO IF "%%i"=="4.0" SET OS_BUILDNUMBER=1381

:Menu
ECHO.
ECHO David.Wang's Sample PHP/IIS Configurator
ECHO Version: June 2006
ECHO OS BuildNumber: %OS_BUILDNUMBER%
ECHO.
ECHO ------------------------------ Summary ------------------------------
ECHO PHP Binaries Dir : %DIR_PHP_FROM%
ECHO PHP Binary Type  : %PHP_TYPE%
ECHO PHP Binary Name  : %PHP_BINARY%
ECHO ---------------------------------------------------------------------

REM
REM Do some basic validations
REM
ECHO.
ECHO Validating inputs...
IF /I ?%PHP_TYPE%? NEQ ?CGI? IF /I ?%PHP_TYPE%? NEQ ?ISAPI? ECHO.&ECHO ERROR: Binary Type MUST be either CGI or ISAPI
FOR %%I IN ( %PHP_BINARY% ) DO (
    IF /I ?%PHP_TYPE%? EQU ?CGI? IF /I ?%%~xI? NEQ ?.exe? ECHO.&ECHO WARNING: Binary Type %PHP_TYPE% requires a CGI EXE binary
    IF /I ?%PHP_TYPE%? EQU ?ISAPI? IF /I ?%%~xI? NEQ ?.dll? ECHO.&ECHO WARNING: Binary Type %PHP_TYPE% requires an ISAPI DLL binary
)
IF /I ?%PHP_TYPE%? EQU ?CGI? SET PHP_MODULE=CgiModule
IF /I ?%PHP_TYPE%? EQU ?CGI? ECHO.&ECHO ERROR: PHP CGI requires modifying cgi.force_redirect to 0 in "%DIR_PHP_FROM%\PHP.INI"
IF /I ?%PHP_BINARY%? NEQ ?php5isapi.dll? IF /I ?%PHP_BINARY%? NEQ ?php-cgi.exe? ECHO.&ECHO WARNING: Unrecognized PHP binary %PHP_BINARY%
Call :VerifyScripts
ECHO.
ECHO Remember to tweak PHP.INI for security and functionality per php.net
ECHO Finished input validation.
ECHO.

SET GO=
SET /P GO=Press 1 to EDIT choices, or ENTER to start IIS modifications:
IF ?%GO%? EQU ?? GOTO :Start

ECHO.
ECHO Press ENTER to accept [%DIR_PHP_FROM%], or provide new value (folder path)
SET /P DIR_PHP_FROM=PHP Binaries Dir:
ECHO Press ENTER to accept [%PHP_TYPE%], or provide new value (CGI or ISAPI)
SET /P PHP_TYPE=PHP Binary Type:
ECHO Press ENTER to accept [%PHP_BINARY%], or provide new value (filename)
SET /P PHP_BINARY=PHP Binary Name:

GOTO :Menu

:Start
REM
REM Start Configuration
REM
ECHO.
ECHO Starting IIS Configuration...
ECHO.
ECHO Copying "%DIR_PHP_FROM%\PHP.INI-Recommended" to PHP.INI...
%DEBUG% COPY /Y "%DIR_PHP_FROM%\PHP.INI-Recommended" "%DIR_PHP_FROM%\PHP.INI"

CALL :VerifyScripts
IF %ERRORLEVEL% EQU 2 GOTO :EOF

REM
REM Use OS Version to distinguish between IIS Versions
REM
REM 1381    NT4     IIS4
REM 2195    W2K     IIS5
REM 2600    WXP     IIS5.1
REM 3790    WS03    IIS6
REM Other   Vista   IIS7
REM
IF %OS_BUILDNUMBER% GTR 3790 (
    ECHO Setting PHP Handler...
    %DEBUG% %CMD_APPCMD% SET CONFIG -section:handlers "-+[name='PHP-%PHP_TYPE%',path='*.php',verb='GET,HEAD,POST',modules='%PHP_MODULE%',scriptProcessor='%DIR_PHP_FROM%\%PHP_BINARY%',resourceType='File']"

    ECHO Adding and Enabling PHP in ISAPI/CGI Restriction List...
    %DEBUG% %CMD_APPCMD% SET CONFIG -section:isapiCgiRestriction "-+[path='%DIR_PHP_FROM%\%PHP_BINARY%',allowed='true',groupId='PHP',description='PHP']"
) ELSE IF %OS_BUILDNUMBER% EQU 3790 (
    ECHO Setting PHP Handler...
    %DEBUG% %CMD_CHGLIST% W3SVC/ScriptMaps "" ".php,%DIR_PHP_FROM%\%PHP_BINARY%,0" /INSERT /COMMIT
    ECHO Adding and Enabling PHP in ISAPI/CGI Restriction List...
    %DEBUG% %CMD_IISEXT% /AddFile "%DIR_PHP_FROM%\%PHP_BINARY%" 1 PHP 1 PHP
) ELSE IF %OS_BUILDNUMBER% LSS 3790 (
    ECHO Setting PHP Handler...
    %DEBUG% %CMD_CHGLIST% W3SVC/ScriptMaps "" ".php,%DIR_PHP_FROM%\%PHP_BINARY%,0" /INSERT /COMMIT
)

ECHO.
ECHO Finished IIS Configuration.
ECHO.
ECHO Test installation using PHP file content of:  ^<?php phpinfo();?^>

ENDLOCAL
GOTO :EOF

REM
REM Sub-routines and Functions
REM
:VerifyScripts
SET ERRORLEVEL=0
IF NOT EXIST "%DIR_PHP_FROM%\%PHP_BINARY%" (
    ECHO.
    ECHO ERROR: PHP Binary "%DIR_PHP_FROM%\%PHP_BINARY%" does not exist!
    ECHO Please first completely extract PHP to "%DIR_PHP_FROM%"
    SET ERRORLEVEL=2
)
IF NOT EXIST "%DIR_PHP_FROM%\PHP.INI" (
    ECHO.
    ECHO ERROR: "%DIR_PHP_FROM%\PHP.INI" does not exist!
    SET ERRORLEVEL=2
)
IF %OS_BUILDNUMBER% GTR 3790 (
    IF NOT EXIST "%CMD_APPCMD%" (
        ECHO.
        ECHO ERROR: Script requires %CMD_APPCMD% for this OS.
        SET ERRORLEVEL=2
    )
)
IF %OS_BUILDNUMBER% EQU 3790 (
    IF NOT EXIST "%FILE_IISEXT%" (
        ECHO.
        ECHO ERROR: Script requires %FILE_IISEXT% for this OS.
        SET ERRORLEVEL=2
    )
)
IF %OS_BUILDNUMBER% LEQ 3790 (
    IF NOT EXIST "%FILE_CHGLIST%" (
        ECHO.
        ECHO ERROR: Script requires %FILE_CHGLIST% for this OS.
        ECHO http://blogs.msdn.com/david.wang/archive/2004/12/02/273681.aspx
        SET ERRORLEVEL=2
    )
)
GOTO :EOF