How to add traces to IIS log from PHP

One of the new features added to FastCGI module in IIS 7.5 is ability to use IIS tracing infrastructure from FastCGI applications. This feature request was made by a customer who wanted to consolidate logging of the request processing in one log. All IIS events were present in IIS trace logs but traces generated by PHP applications were separate. So IIS trace log didn’t give a holistic view for PHP applications. ASP.Net already has a way to use IIS traces. So this limitation was specific to PHP. We closed this gap in PHP by providing a way for PHP developers to send traces to IIS using FastCGI’s STDERR stream which are then extracted by FastCGI module and sent to IIS tracing subsystem. This feature has been included in KB 980363 so that this can be made to work on IIS 7.0 as well. On IIS6 and below, these trace messages will be ignored as there is no tracing subsystem available.
 
FastCGI module parses STDERR stream looking for markers which identify the content as trace messages. Messages starting with “IIS_TRACE_ERROR:” are parsed as errors, “IIS_TRACE_WARNING:” as warnings and “IIS_TRACE_INFO” as info messages. Message end is recognized by marker “:IIS_TRACE_END”. Any function in PHP which can write to STDERR stream can be used to send these traces to FastCGI. So calling error_log(“IIS_TRACE_ERROR:This is an error.:IIS_TRACE_END”) will appear in IIS trace log as an error. Traces appear for CGI trace provider on IIS 7.0 and FastCGI trace provider on IIS 7.5. Trace messages are removed from STDERR stream before STDERR stream content goes through StderrMode logic so that traces don’t disturb existing STDERR behavior. Below is a simple helper class which you can use to generate trace messages.
 
<?php
 
class IIS_TRACE {
 
 const NONE    = 0;
 const ERROR   = 1;
 const WARNING = 2;
 const INFO    = 3;
 
 private static $verbosity = self::NONE;
 
 private static $traceErrorStart   = "IIS_TRACE_ERROR:";
 private static $traceWarningStart = "IIS_TRACE_WARNING:";
 private static $traceInfoStart    = "IIS_TRACE_INFO:";
 private static $traceEnd          = ":IIS_TRACE_END";
 
 public static function SetVerbosity ( $verbose )
 {
    if( $verbose != self::NONE    && $verbose != self::ERROR   &&
        $verbose != self::WARNING && $verbose != self::INFO )
    {
        throw new Exception( 'Function SetVerbosity called with invalid value' );
    }
 
    self::$verbosity = $verbose;
 }
 
 public static function WriteError ( $msg )
 {
    if (self::$verbosity >= self::ERROR )
        error_log( self::$traceErrorStart . $msg . self::$traceEnd );
 }
 
 public static function WriteWarning ( $msg )
 {
    if (self::$verbosity >= self::WARNING )
        error_log( self::$traceWarningStart . $msg . self::$traceEnd );
 }
 
 public static function WriteInfo ( $msg )
 {
    if (self::$verbosity >= self::INFO )
    error_log( self::$traceInfoStart . $msg .self::$traceEnd );
 }
}
 
?>
 
With this class present in your code, you can use WriteInfo, WriteWarning, WriteError functions available in IIS_TRACE class in your code to add traces to IIS trace log as below. When you have tracing enabled, these traces will appear with all other information about request processing.
 
<?php
 
require_once(‘iistrace.php’);
.
.
If($percent > 100)
{
    IIS_TRACE::WriteError(‘Invalid data provided. Percent = ’ . $percent);
}
.
.
 
?>
 
Hope this helps.
Kanwal

Application specific mapping in FastCGI ISAPI 1.5

IIS team recently released FastCGI ISAPI 1.5 for IIS 5.1 and IIS 6.0. One of the major new features added in this release is per application process pools. Per application process pools allow users to specify a unique FastCGI mapping for a particular web application in fcgiext.ini. Using this functionality administrators can now choose to run a web application with PHP version which can be different from what is used for rest of the PHP applications in the same site or the server. This was always possible in IIS 7.0 and 7.5 by overriding PHP handler in web.config but not on WinXP and Win2K3. Version 1.0 of FastCGI ISAPI only allowed specifying a different process pool at a site level granularity. So if a site has both wordpress and gallery installed, it wasn’t possible to use different PHP versions for running them. With FastCGI 1.5 that is possible now. For providing application specific mappings, we extended site specific mapping feature. Let’s learn application specific mapping by comparing it with site specific mapping.
 

 

 
Site specific mapping
Application specific mapping
fcgiext.ini
[Types]
php=PHP_53
php:1234=PHP_52
 
[PHP_53]
ExePath=c:\php530\php-cgi.exe
 
[PHP_52]
ExePath=c:\php520\php-cgi.exe
[Types]
Php=PHP_530
Php:1234=PHP_526
Php:/w3svc/1234/root/wordpress=PHP_530
Php:/w3svc/1234/root/gallery=PHP_520

[PHP_526]
.
.
.

Details
Above configuration will make FastCGI module pick PHP_53 mapping for all sites but use PHP_52 mapping for site with id 1234.
FastCGI module will pick PHP_520 mapping for gallery and PHP_530 for wordpress defined under site 1234. All other php applications under site 1234 will be handled using PHP_526 mapping. Remaining sites will use PHP_530 mapping.

Application path’s format for fcgiext.ini is “/LM/W3SVC/<siteId>/ROOT/<appname>”. In this format “/LM” and “/W3SVC” parts are option. “/Root” can be emitted for applications pointing to root of the site.

fcgiconfig.js
fcgiconfig.js -add -section:php_530 -extension:php -path:c:\php530\php-cgi.exe -site:1234
fcgiconfig.js -add -section:php_530 -extension:php -path:c:\php530\php-cgi.exe -application:/w3svc/1234/root/wordpress
 
 
Picking the right mapping
Among all the formats (including wildcard formats) which are accepted in fcgiext.ini mappings, FastCGI goes through following logic to pick which mapping to use for each request.
 
·         If mapping for current  application is present, it takes highest priority and is used. E.g. php:/w3svc/1234/root/drupal=ApplicationHandler
·         If wildcard mapping for the current application is present, it is used next. E.g. *:/w3svc/1234/root/drupal=ApplicationHandler
·         If mapping for a particular site is present but application specific mapping is not present, than that is used. E.g. php:1234=SiteHandler
·         If site specific and application specific mapping is not present, FastCGI module look for wildcard mapping for a particular site. E.g. *:1234=SiteHandler
·         If none of the above is present, mapping for the file extension takes priority. E.g. php=ExtensionHandler
·         Wildcard mapping comes last. *=WildcardHandler
 
If FastCGI doesn’t find a mapping after going through the logic above, an error is returned.
 
What’s coming next
 
Now that applications can be configured to run using a particular PHP version on IIS 5.1 and above, our next goal is to support multiple PHP versions side-by-side in WebPI. This would require PHP installer to support side-by-side install which is also something we are looking at. Once PHP installer has the required support, we will list different PHP versions in WebPI and let users install multiple versions of PHP. Additionally users will be able to target a particular PHP version when installing a PHP application using WebPI.
 
Hope this helps.
Kanwal

Using windows live as SMTP relay server

Today I started writing a program to notify me when IP assigned to my router by my broadband provider changes. I wanted this program to check IP address of the router every few minutes and send me email if it detects that IP is changed. For sending email, I used System.Net.Mail.SmtpClient which requires a smtp host. Instead of running a smtp server on my machine, I started looking for free smtp relay servers available on the internet. I was surprised to find out that there are plenty of them available. In fact most of the popular email service providers like windows live, yahoo, gmail offer smtp relay services. You can read more about smtp relay servers and get a list of freely available smtp relay servers here.

Below is the code I used to send email notification using windows live smtp relay. If you want to use gmail, you can use "smtp.gmail.com" as the smtphost with gmail credentials.

static
bool SendEmailNotification(string newIpAddress)
{
    bool success = false;

    //
    // Send email using live account
    //
    SmtpClient mailClient = new SmtpClient("smtp.live.com", 587);
    mailClient.UseDefaultCredentials = false;
    mailClient.EnableSsl = true;

    MailMessage message = new MailMessage("WindowsLiveUserName@live.com", destination@domain);
    message.Subject = "Require immediate action – IP address change";
    message.Body = "New IP address is " + newIpAddress;
    message.Priority = MailPriority.High;

    NetworkCredential credentials =
       
new NetworkCredential("WindowsLiveUserName@live.com", "YourPassword", "");
    mailClient.Credentials = credentials;

    try
    {
        mailClient.Send(message);
        success = true;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }

    return success;
} 

 

I am still making some changes to my IpChangeMailer program. I will make it available for download once it is complete.

Thanks.
Kanwal

WaitForAllObjects to wait on more than MAXIMUM_WAIT_OBJECTS handles

One of the painful limitations of WaitForMultipleObjects function is that you can use it to wait for a maximum of MAXIMUM_WAIT_OBJECTS number of object handles. Value of MAXIMUM_WAIT_OBJECTS is 64. If you need to wait on more than MAXIMUM_WAIT_OBJECTS handles, you can either create a separate thread to wait on MAXIMUM_WAIT_OBJECTS and then do a wait on these threads to finish. Using this method you can create MAXIMUM_WAIT_OBJECTS threads each of those can wait for MAXIMUM_WAIT_OBJECTS object handles. Few months ago I needed code for doing this and couldn't find it on the internet. Here is how I implemented WaitForAllObjects.

typedef
struct _WAIT_THREAD_INFORMATION
{
    DWORD      dwWaitObjectCount;
    PHANDLE    pWaitHandles;
    DWORD      dwWaitMSeconds;
} WAIT_THREAD_INFORMATION, *PWAIT_THREAD_INFORMATION;

VOID
WaitForAllObjects(
    DWORD     dwWaitHandleCount,
    HANDLE *  pWaitHandles,
    DWORD     dwMaxWaitMSeconds
)
{
    HANDLE *                  pThreadWaitHandles    = NULL;
    WAIT_THREAD_INFORMATION * pWaitInformationArray = NULL;

    DWORD                     dwThreadCount         = 0;
    DWORD                     dwLastWaitCount       = 0;
    DWORD                     dwWaitHandleIndex     = 0;
    DWORD                     dwMaximumWaitObjects  = MAXIMUM_WAIT_OBJECTS;

    ASSERT( dwWaitHandleCount    >  0 );
    ASSERT( pWaitHandles         != NULL );
    ASSERT( dwMaxWaitMSeconds    >= 1000 );
    ASSERT( dwMaximumWaitObjects <= MAXIMUM_WAIT_OBJECTS );

    if( dwWaitHandleCount <= dwMaximumWaitObjects )
    {
        WaitForMultipleObjects( dwWaitHandleCount,
                                pWaitHandles,
                                TRUE,
                                dwMaxWaitMSeconds );
    }
    else
    {
        //
        // Create separate threads to wait on maximum wait objects
        // and then make this thread wait on thread handles
        //
        dwThreadCount   = dwWaitHandleCount / dwMaximumWaitObjects;
        dwLastWaitCount = dwWaitHandleCount % dwMaximumWaitObjects;

        if( dwLastWaitCount > 0 )
        {
            dwThreadCount++;
        }

        //
        // This function can handle a maximum of
        // MAXIMUM_WAIT_OBJECTS * MAXIMUM_WAIT_OBJECT handles
        //
        if( dwThreadCount > dwMaximumWaitObjects )
        {
            dwThreadCount     = dwMaximumWaitObjects;
            dwLastWaitCount   = 0;
        }

        pThreadWaitHandles    = new HANDLE[ dwThreadCount ];
        pWaitInformationArray = new WAIT_THREAD_INFORMATION[ dwThreadCount ];

        if( pThreadWaitHandles == NULL || pWaitInformationArray == NULL )
        {
            //
            // Failure
            //
            goto Finished;
        }

        for( DWORD count = 0; count < dwThreadCount; count++)
        {
            //
            // Set information for the thread
            //
            pWaitInformationArray[ count ].dwWaitMSeconds =
                dwMaxWaitMSeconds;
            pWaitInformationArray[ count ].pWaitHandles =
                &pWaitHandles[ dwWaitHandleIndex ];

            if( count != dwThreadCount - 1 || dwLastWaitCount == 0 )
            {
                pWaitInformationArray[ count ].dwWaitObjectCount =
                    dwMaximumWaitObjects;
                dwWaitHandleIndex += dwMaximumWaitObjects;
            }
            else
            {
                pWaitInformationArray[count].dwWaitObjectCount =
                    dwLastWaitCount;
                dwWaitHandleIndex += dwLastWaitCount;
            }

            pThreadWaitHandles[ count ] =
                CreateThread( NULL,
                              0,
                              WaitForMultipleObjectsThread,
                              &pWaitInformationArray[ count ],
                              0,
                              NULL );
            if( pThreadWaitHandles[ count ] == NULL )
            {
                //
                // Not able to create threads break from the loop
                // and wait for threads
we already created.
                // dwThreadCount doesnt include this iteration
                //
                dwThreadCount = count;
                break;
            }
        }

        //
        // Failure is ignored
        //
        WaitForMultipleObjects( dwThreadCount,
                                pThreadWaitHandles,
                                TRUE,
                                dwMaxWaitMSeconds );
    }

Finished:

    if( pThreadWaitHandles != NULL )
    {
        for( DWORD count = 0; count < dwThreadCount; count++)
        {
            if( pThreadWaitHandles[ count ] != NULL )
            {
                CloseHandle( pThreadWaitHandles[ count ] );
                pThreadWaitHandles[ count ] = NULL;
            }
        }

        delete [] pThreadWaitHandles;
        pThreadWaitHandles = NULL;
    }

    if( pWaitInformationArray != NULL )
    {
        delete [] pWaitInformationArray;
        pWaitInformationArray = NULL;
    }

    return;
}

//static
DWORD
WINAPI
WaitForMultipleObjectsThread(
    LPVOID lpParameter = NULL
)
//
// Thread routine which calls WaitForMultipleObjects
// This is used to wait for more than MAXIMUM_WAIT_OBJECTS objects
//
{
    DWORD                    dwReturnValue      = 0;
    PWAIT_THREAD_INFORMATION pThreadInformation = NULL;

    ASSERT( lpParameter != NULL );

    pThreadInformation = ( PWAIT_THREAD_INFORMATION )lpParameter;

    ASSERT(pThreadInformation->dwWaitObjectCount <= MAXIMUM_WAIT_OBJECTS );
    ASSERT(pThreadInformation->pWaitHandles      != NULL );
    ASSERT(pThreadInformation->dwWaitMSeconds    != INFINITE );
    ASSERT(pThreadInformation->dwWaitMSeconds    >= 1000 );

    dwReturnValue = WaitForMultipleObjects(
                        pThreadInformation->dwWaitObjectCount,
                        pThreadInformation->pWaitHandles,
                        TRUE,
                        pThreadInformation->dwWaitMSeconds );

    return dwReturnValue;

}

Sample usage of this method will look like below.

//
// Allocate memory for wait handle array
//
pWaitHandles = new HANDLE[ dwWaitHandleCount ];

...

//
// Call WaitForAllObjects
//
WaitForAllObjects( dwWaitHandleCount,
                   pWaitHandles,
                   dwMaximumWait );

...

//
// Free memory held by pWaitHandles
//
if( pWaitHandles != NULL )
{
    delete [] pWaitHandles;
    pWaitHandles = NULL;
}

Hope this helps.
Kanwal

Urlscan to RequestFiltering migration using MSDeploy

In addition to FastCGI migration provider, MSDeploy 1.0 RTW shipped with a URLScan to request filtering migration provider to ease migration of UrlScan.ini settings to system.webServer/security/requestFiltering section. Even though URLScan 3.1 is supported on Win2K8 and you are not required to move to request filtering module, there are few advantages in using request filtering module. One advantage is that all your configuration can stay together in applicationHost.config and web.config and you are not required to maintain a separate configuration file. Another advantage is that you can take advantages of new configuration system features like distributed configuration, shared configuration, locking, ability to use appcmd, UI, configuration editor etc which cannot be used if you use UrlScan and your configuration is in UrlScan.ini. In Win2K8 R2, you get additional advantages like configuration system auditing. Moreover, request filtering is one of the core IIS modules and will continue to get much attention compared to URLScan. If you are running Win2K8 SP2 or Win2K8 R2 in which request filtering module has all the features available in URLScan 3.1, you should definitely evaluate migrating from URLScan to request filtering. If you decide to migrate, migration is as simple as running a MSDeploy sync command.

URLScan migration is handled by a new provider in msdeploy named UrlScanConfig which accepts “INI” or “APPHOST” as path. When path is “INI”, URLScan configuration from “%windir%\system32\inetsrv\urlscan\urlscan.ini” is read. When path is “APPHOST”, configuration is picked from system.webServer/security/requestFiltering section. This migration provider works similar to FastCGI migration provider. UrlScanConfig migration provider reads UrlScan.ini configuration and produces xml which looks like requestFiltering section configuration. MSDeploy engine then takes care of comparing xml and doing add/update/delete operations on destination to make destination configuration same as source. Because msdeploy sync is a single master sync engine, we take care of not removing configuration from requestFiltering section which doesn’t have a counterpart in UrlScan. For example, hiddenSegments configuration in apphost is not deleted (skipped using urlScanSkipIncompatRuleHandler) and also applyToWebDav properties are not touched. Below are few examples showing the usage of UrlScanConfig migration provider.

Command to dump urlscan.ini settings.
        msdeploy –verb:dump –source:urlScanConfig=ini –xml
        msdeploy –verb:dump –source:UrlScanConfig=apphost -xml

Command to migrate urlscan.ini to requestFiltering section.
        msdeploy –verb:sync –source:urlScanConfig=ini –dest:urlScanConfig=apphost -whatif

UrlScanConfig provider can be used to migrate global urlscan.ini configuration to server level requestFiltering section. Migrating site level UrlScan.ini is not supported. Here is how various URLScan properties map to requestFiltering section.

 

UseAllowVerbs

If set to 1 AllowVerbs section is used. Else DenyVerbs section in urlscan.ini is used

UseAllowExtensions

If set to 1 AllowExtensions section is used. Else DenyExtensions section in urlscan.ini is used

VerifyNormalization

allowDoubleEscaping

AllowHighBitCharacters

allowHighBitCharacters

UnescapeQueryString

unescapeQueryString

 

RequestLimits

MaxAllowedContentLength, MaxUrl and MaxQueryString settings are moved to requestLimits.

AllowVerbs, DenyVerbs

If UseAllowVerbs is 1, verbs@allowUnlisted is set to false and entries are added with enabled=”true”. If UseAllowVerbs is 0, entries has enabled=”false” and verbs@allowUnlisted is set to true.

AllowExtensions, DenyExtensions

When UseAllowExtensions is set 1, extensions are added with enabled=”true” and fileExtensions@allowUnlisted is set to false. When UseAllowExtensions is 0, fileExtensions@allowUnlisted is set to true and entries are added with enabled=”false”.

DenyHeaders

Moved to requestLimits/headerLimits after removing colon.

AlwaysAllowedUrls

alwaysAllowedUrls collection

DenyUrlSequences

denyUrlSequences collection

AlwaysAllowedQueryStrings

alwaysAllowedQueryStrings collection

DenyQueryStringSequences

denyQueryStringSequences collection

 

For each rule in RulesList, a filteringRule is created and Rule properties are mapped as following.

AppliesTo

filteringRule/appliesTo

DenyDataSection

filteringRule/denyStrings.

ScanURL

filteringRule@scanUrl

ScanAllRaw

filteringRule@scanAllRaw

ScanQueryString

filteringRule@scanQueryString

ScanHeaders

filteringRule/scanHeaders

 

All other properties (NormalizeUrlBeforeScan, AllowDotInPat, RemoveServerHeader, AlternateServerName, AllowLateScanning, UseFastPathReject, RejectResponseUrl, EnableLogging, PerProcessLogging, PerDayLogging, LogLongUrls, LoggingDirectory) are ignored because either they don’t make sense or the feature is always enforced by IIS core. We do block migration when incompatible versions of source and destination are present. Some request filtering features like applyToWebDav and hiddenSegments were not present in webDav. urlscanConfig migration provider doesn’t touch these properties if they are present on target. Not that if version of UrlScan is not compatible with IIS version present, migration will not be performed.

Thanks,
Kanwal