Search Blog

Author: Joe Meyer
Date: January 2, 2014

Goal: To allow .php files to run on a windows IIS 7.5 web server

Difficulty: Medium

Prerequisites: IIS 7.5 Webserver

With PHP being such a popular programming language for the web, it is important to understand how we can leverage our windows web server to run php. Below I will demonstrate how to install PHP 5.4 onto my brand new IIS7 web server. This server is running Windows 2008 r2 standard edition. A couple of disclaimers before I get started with the basic guide: You will need to adjust the paths in this guide suitable to your environment, I am using C: for my default website and S: (Secondary) drive for my program files (aka php installation). You will also need to make sure that you have the appropriate C++ runtime environment on your operating system. This is a fairly standard requirement so it is not included in this guide. Lastly, if you run into any problems or have question please feel free to reply to this thread or create your own and I will be more than happy to help you figure out what's going on (in my experience if you follow this guide and something isn't working it's a permission problem, or a php.ini configuration problem)

  1. The first step to making sure you can use PHP on your IIS server it to make sure you have CGI enabled. This can be done from the Server Manager under the Roles section.
    IIS7.5 with PHP 1.png
  2. Next leats head out to http://php.net/downloads.php and select the Windows {Version} binaries and source link.
    IIS7.5 with PHP 2.png
  3. Next make sure you download the "Non Thread Safe" zip file of the version you want. You may also notice that it says VC9 x86, this indicates that you will need the x86 version of the c++ 2008 package on your machine in order for php to run.
    IIS7.5 with PHP 3.png
  4. You will then be presented with some sort of download menu, if your using a browser that supports it just go ahead and open the zip file.
    IIS7.5 with PHP 4.png
  5. After you have the zip file opened go ahead and select "Extract all files" near the top left corner of the window.
    IIS7.5 with PHP 5.png
  6. Now extract these files to wherever you want your php installation to be located. For me I have this on a secondary drive under Program Files. Following the extraction go ahead and open up that folder as we have some work to do in that directory before we're done.
    IIS7.5 with PHP 6.png
  7. In your PHP installation directory copy your php.ini-production (or development depending on what kind of sites you are putting up on this box).
    IIS7.5 with PHP 7.png
  8. Then paste this file back in and rename it to be "php.ini".
    IIS7.5 with PHP 8.png
  9. In your php.ini file make sure you have the following set:
    open_basedir = {YOUR IIS WEBSITES PATH HERE}
    cgi.fix_pathinfo=1
    fastcgi.impersonate=1
    IIS7.5 with PHP 9.png
  10. After this set up your extensions you wish to have enabled and configure the rest of your php.ini file to your liking. Save and close the file.
    IIS7.5 with PHP 10.png
  11. Next Open up the Internet Information Services (IIS) Manager.
    IIS7.5 with PHP 11.png
  12. Select teh "Handler Mappings" for your server
    IIS7.5 with PHP 12.png
  13. Then click the "Add Module Mapping..." found in the top right corner of the Handler Mappings screen.
    IIS7.5 with PHP 13.png
  14. Then fill in the following information and click OK:
    Request Path:  *.php
    Module: FastCgiModule
    Executable (optional): [Enter the location of your php installation]\php-cgi.exe
    Name: PHP Via FastCGI
    IIS7.5 with PHP 14.png
  15. After you click OK you will be prompted: "Do you want to create a FastCGI application for this executable? Click 'Yes' to add the entry to the FastCGI collection and to enable this executable to run as a FastCGI application." to which you should click "Yes".
    IIS7.5 with PHP 15.png
  16. At this point you should have php running on your IIS server. To test, create a file called phpinfo.php in your default website directory (default is C:\inetpub\wwwroot\) and put the following code in the file and save it.
    <?php
    phpinfo();
    ?>
    IIS7.5 with PHP 16.png
  17. After you have added that file open a web browser on the server and go to http://localhost/phpinfo.php and you should see your php information if php is working properly. If not revisit the steps above and try and find what you might have missed.
    IIS7.5 with PHP17.png

Author: Joe Meyer
Date: January 2, 2014

Often times the question comes up as to what purpose reverse DNS really does for us. To be honestly, I haven't found anything that ptr records MUST be set up for. However, here are some uses that you may encounter them being used for:

  • Network Troubleshooting using tools such as traceroute, ping, or the "Received" header field in SMTP emails. Many web sites use tools such as these to help track users as a reverse DNS name will often mean a lot more to a webmaster than an IP will when trying to figure out if someone is the same user, so ISP's having rDNS set up when they hand out IPs is useful.
  • Along the same lines of network troubleshooting is System logs or Monitoring tools. These often receive the IP address and will try to do a reverse lookup to put in the logs. Again quite useful for system admins or network admins as an IP might not mean much to them.
  • Email anti-spam techniques often use domain names for the rDNS record to see if these domains have rDNS records of something that doesn't match the domain of the sender, or if the record looks like it's coming from a dynamic IP address. According to spamhaus, a global email blacklist database, these are things that could raise the spam score of an email.

Microsoft DNS servers by default add reverse dns lookup. This is a very useful tool for internal debugging as said above, however when it boils down to setting up this record for your webhosting domains, if you aren't planning on sending email from that IP it's probably not worth your effort to add the records.

Another common issue is that many webhosts do not support ptr record setups. If however you have a dedicated IP then your webhost may be able to have the ptr record added. There is no restriction on the number of ptr records for a domain.

If you wish to look up a rdns (pointer) record for an IP address you can use the nslookup command and use the following commands:

nslookup
set type=ptr
[enter ip address and hit enter]

Author: Joe Meyer
Date: January 2, 2014

Goal: Output Directories a specified number of directories deep with their directory permissions.

Difficulty: Medium

Prerequisites: Visual Studios or other C# Compiler.

Attached is a simple script that will find all directories n levels deep on the directory specified to search, and output the permissions associated with those directories. This is a C# Console application. You just need to build it. I make use of the args variable in the main method so that I can run this application and then send it to a file using >. This program also catches any directories it does not have access to and outputs "Could not access <Directory>".

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.AccessControl;
namespace FolderPermissions
{
    class Program
    {
        static void GetDirectorySecurity(string dir, int levels)
        {
            int curLevel = 1;
            string[] dirs = Directory.GetDirectories(dir);
            foreach (string directory in dirs)
            {
                Console.WriteLine("---------------------------------------------------------");
                Console.WriteLine(directory);
                try
                {
                    string tabs = "\t";
                    DirectoryInfo dInfo = new DirectoryInfo(directory);
                    DirectorySecurity dSecurity = dInfo.GetAccessControl();
                    AuthorizationRuleCollection acl = dSecurity.GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount));
                    foreach (FileSystemAccessRule ace in acl)
                    {
                        Console.WriteLine("{0}Account: {1}", tabs, ace.IdentityReference.Value);
                        Console.WriteLine("{0}Type: {1}", tabs, ace.AccessControlType);
                        Console.WriteLine("{0}Rights: {1}", tabs, ace.FileSystemRights);
                        Console.WriteLine("{0}Inherited: {1}", tabs, ace.IsInherited);
                        Console.WriteLine();
                    }
                    if (curLevel < levels)
                        GetDirectorySecurity(@directory, curLevel + 1, levels);
                }
                catch
                {
                    Console.WriteLine("Could not access {0}", directory);
                }
            }
        }
        static void GetDirectorySecurity(string dir, int curLevel, int levels)
        {
            string[] dirs = Directory.GetDirectories(@dir);
            string tabs = "";
            for (int i = 0; i < curLevel; i++)
                tabs += "\t";
            foreach (string directory in dirs)
            {
                Console.WriteLine(tabs.Substring(0, tabs.Length - 1) + "---------------------------------------------------------");
                Console.WriteLine(tabs.Substring(0, tabs.Length - 1) + directory);
                try
                {
                    DirectoryInfo dInfo = new DirectoryInfo(directory);
                    DirectorySecurity dSecurity = dInfo.GetAccessControl();
                    AuthorizationRuleCollection acl = dSecurity.GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount));
                    foreach (FileSystemAccessRule ace in acl)
                    {
                        Console.WriteLine("{0}Account: {1}", tabs, ace.IdentityReference.Value);
                        Console.WriteLine("{0}Type: {1}", tabs, ace.AccessControlType);
                        Console.WriteLine("{0}Rights: {1}", tabs, ace.FileSystemRights);
                        Console.WriteLine("{0}Inherited: {1}", tabs, ace.IsInherited);
                        Console.WriteLine();
                    }
                    if (curLevel < levels)
                        GetDirectorySecurity(@directory, curLevel + 1, levels);
                }
                catch
                {
                    Console.WriteLine("Could not access {0}", directory);
                }
            }
        }
        static void Main(string[] args)
        {
            try
            {
                if (args[0] != null && args[1] != null)
                    GetDirectorySecurity(@args[0], int.Parse(args[1]));
                else
                {
                    Console.WriteLine("This program requires the input of a starting directory path");
                    Console.WriteLine("including the letter drive followed by an integer specifying");
                    Console.WriteLine("how many directories deep to recursively scan");
                    Console.WriteLine();
                    Console.WriteLine("Example: programname C:\\shared\\ 2");
                }
            }
            catch
            {
                {
                    Console.WriteLine("This program requires the input of a starting directory path");
                    Console.WriteLine("including the letter drive followed by an integer specifying");
                    Console.WriteLine("how many directories deep to recursively scan");
                    Console.WriteLine();
                    Console.WriteLine("Example: FolderPermissions C:\\shared\\ 2");
                    Console.WriteLine("\tThis will scan 2 levels deep on the shared folder on C:");
                }
            }
        }
    }
}

Attached is my program solution built in visual studios 2010 for those of you who wish to use that instead of copying and pasting the code into a new project.

FolderPermissionsFinder.zip

Author: Joe Meyer
Date: January 2, 2014

Goal: Remove any secondary/alternate SMTP addresses for an entire domain

Difficulty: Easy

Prerequisites: Administrative access to mailboxes

In Microsoft Exchange it is easy enough to mistakenly add an smtp alias to every existing account when you add another domain to your Exchange Server. Fortunately, it's easy enough to get rid of all these unwanted secondary aliases for that domain and not remove any mailboxes that have that domain as the primary account. Simply copy the sample below, change the domain you want to remove, and run the file from a powershell console. When the script finishes you can navigate to c:\addressesRemoved.txt to get a list of all of the addresses that were removed in case you need some form of auditing.

If you want to do a dry run you can remove lines 9 and 10 below and run the script and check the file to see what is going to be removed.

$Mailboxes = Get-Mailbox -result unlimited
$Mailboxes | foreach{
    for ($i=0;$i -lt $_.EmailAddresses.Count; $i++)
    {
        $address = $_.EmailAddresses[$i]
        if ($address.IsPrimaryAddress -eq $false -and $address.SmtpAddress -like "*domainToRemove.com" )
        {
            Write-host($address.AddressString.ToString() | out-file c:\addressesRemoved.txt -append )
            $_.EmailAddresses.RemoveAt($i)
            $i--
        }
    }
    Set-Mailbox -Identity $_.Identity -EmailAddresses $_.EmailAddresses
}

 

Special thanks to Jon Brelie for reporting a bug in the script and an appropriate fix. Script was updated 5/7/2014 to reflect this patch.

Author: Joe Meyer
Date: January 2, 2014

This is a program I ended up doing for a homework assignment back in the day. It does serve some practical purpose on occasion, but is also a great learning tool for recursion. I did take a little bit of time to go back through and have the program handle positive and negative doubles and quite large ones at that. One practical application you might see this used in could be check writing. 

This is a C# Console Application code so simply load up visual studios (or your favorite C# compiler) and drop this code on into a Console application .cs file. Click Run, and you're ready to start converting.

using System;
using System.Collections.Generic;
using System.Text;

namespace NumWords
{
    class Program
    {
        // PROGRAM HANDLES NEGATIVE AND POSITIVE DOUBLES


        static String NumWordsWrapper(double n)
        {
            string words = "";
            double intPart;
            double decPart = 0;
            if (n == 0)
                return "zero";
            try {
                string[] splitter = n.ToString().Split('.');
                intPart = double.Parse(splitter[0]);
                decPart = double.Parse(splitter[1]);
            } catch {
                intPart = n;
            }

            words = NumWords(intPart);

            if (decPart > 0) {
                if (words != "")
                    words += " and ";
                int counter = decPart.ToString().Length;
                switch (counter) {
                    case 1: words += NumWords(decPart) + " tenths"; break;
                    case 2: words += NumWords(decPart) + " hundredths"; break;
                    case 3: words += NumWords(decPart) + " thousandths"; break;
                    case 4: words += NumWords(decPart) + " ten-thousandths"; break;
                    case 5: words += NumWords(decPart) + " hundred-thousandths"; break;
                    case 6: words += NumWords(decPart) + " millionths"; break;
                    case 7: words += NumWords(decPart) + " ten-millionths"; break;
                }
            }
            return words;
        }

        static String NumWords(double n) //converts double to words
        {
            string[] numbersArr = new string[] { "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" };
            string[] tensArr = new string[] { "twenty", "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninty" };
            string[] suffixesArr = new string[] { "thousand", "million", "billion", "trillion", "quadrillion", "quintillion", "sextillion", "septillion", "octillion", "nonillion", "decillion", "undecillion", "duodecillion", "tredecillion", "Quattuordecillion", "Quindecillion", "Sexdecillion", "Septdecillion", "Octodecillion", "Novemdecillion", "Vigintillion" };
            string words = "";

            bool tens = false;

            if (n < 0) {
                words += "negative ";
                n *= -1;
            }

            int power = (suffixesArr.Length + 1) * 3;

            while (power > 3) {
                double pow = Math.Pow(10, power);
                if (n >= pow) {
                    if (n % pow > 0) {
                        words += NumWords(Math.Floor(n / pow)) + " " + suffixesArr[(power / 3) - 1] + ", ";
                    } else if (n % pow == 0) {
                        words += NumWords(Math.Floor(n / pow)) + " " + suffixesArr[(power / 3) - 1];
                    }
                    n %= pow;
                }
                power -= 3;
            }
            if (n >= 1000) {
                if (n % 1000 > 0) words += NumWords(Math.Floor(n / 1000)) + " thousand, ";
                else words += NumWords(Math.Floor(n / 1000)) + " thousand";
                n %= 1000;
            }
            if (0 <= n && n <= 999) {
                if ((int)n / 100 > 0) {
                    words += NumWords(Math.Floor(n / 100)) + " hundred";
                    n %= 100;
                }
                if ((int)n / 10 > 1) {
                    if (words != "")
                        words += " ";
                    words += tensArr[(int)n / 10 - 2];
                    tens = true;
                    n %= 10;
                }

                if (n < 20 && n > 0) {
                    if (words != "" && tens == false)
                        words += " ";
                    words += (tens ? "-" + numbersArr[(int)n - 1] : numbersArr[(int)n - 1]);
                    n -= Math.Floor(n);
                }
            }

            return words;

        }
        static void Main(string[] args)
        {
            Console.Write("Enter a number to convert to words: ");
            Double n = Double.Parse(Console.ReadLine());

            Console.WriteLine("{0}", NumWordsWrapper(n));
        }
    }
}

Author: Joe Meyer
Date: January 2, 2014

Goal: Send a calendar invite using PHP's mail function

Difficulty: Medium

Prerequisites: A good understanding of PHP is helpful

Awhile back I made an attempt to build a course scheduling software with PHP. One thing that we found to be extremely helpful in our environment was the ability to send calendar invites as soon as someone signed up for the course, automatically. With PHP this was quite a process and I still won't claim that this is fool proof but I was able to get it working with our exchange 2010 server and gmail users as well. If anyone has feedback to improve the post please let me know as I'd love to have something even more robust and better tested. 

The concept behind this is pretty simple, I leveraged PHP's mail function and set mime bounderies to send an ICAL / html email. I wrapped these all inside a relatively neat function which has worked well for my current environment. So here's the function:

<?php
function sendIcalEvent($from_name, $from_address, $to_name, $to_address, $startTime, $endTime, $subject, $description, $location)
{
    $domain = 'exchangecore.com';

    //Create Email Headers
    $mime_boundary = "----Meeting Booking----".MD5(TIME());

    $headers = "From: ".$from_name." <".$from_address.">\n";
    $headers .= "Reply-To: ".$from_name." <".$from_address.">\n";
    $headers .= "MIME-Version: 1.0\n";
    $headers .= "Content-Type: multipart/alternative; boundary=\"$mime_boundary\"\n";
    $headers .= "Content-class: urn:content-classes:calendarmessage\n";
    
    //Create Email Body (HTML)
    $message = "--$mime_boundary\r\n";
    $message .= "Content-Type: text/html; charset=UTF-8\n";
    $message .= "Content-Transfer-Encoding: 8bit\n\n";
    $message .= "<html>\n";
    $message .= "<body>\n";
    $message .= '<p>Dear '.$to_name.',</p>';
    $message .= '<p>'.$description.'</p>';
    $message .= "</body>\n";
    $message .= "</html>\n";
    $message .= "--$mime_boundary\r\n";

    $ical = 'BEGIN:VCALENDAR' . "\r\n" .
    'PRODID:-//Microsoft Corporation//Outlook 10.0 MIMEDIR//EN' . "\r\n" .
    'VERSION:2.0' . "\r\n" .
    'METHOD:REQUEST' . "\r\n" .
    'BEGIN:VTIMEZONE' . "\r\n" .
    'TZID:Eastern Time' . "\r\n" .
    'BEGIN:STANDARD' . "\r\n" .
    'DTSTART:20091101T020000' . "\r\n" .
    'RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11' . "\r\n" .
    'TZOFFSETFROM:-0400' . "\r\n" .
    'TZOFFSETTO:-0500' . "\r\n" .
    'TZNAME:EST' . "\r\n" .
    'END:STANDARD' . "\r\n" .
    'BEGIN:DAYLIGHT' . "\r\n" .
    'DTSTART:20090301T020000' . "\r\n" .
    'RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3' . "\r\n" .
    'TZOFFSETFROM:-0500' . "\r\n" .
    'TZOFFSETTO:-0400' . "\r\n" .
    'TZNAME:EDST' . "\r\n" .
    'END:DAYLIGHT' . "\r\n" .
    'END:VTIMEZONE' . "\r\n" .	
    'BEGIN:VEVENT' . "\r\n" .
    'ORGANIZER;CN="'.$from_name.'":MAILTO:'.$from_address. "\r\n" .
    'ATTENDEE;CN="'.$to_name.'";ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:'.$to_address. "\r\n" .
    'LAST-MODIFIED:' . date("Ymd\TGis") . "\r\n" .
    'UID:'.date("Ymd\TGis", strtotime($startTime)).rand()."@".$domain."\r\n" .
    'DTSTAMP:'.date("Ymd\TGis"). "\r\n" .
    'DTSTART;TZID="Eastern Time":'.date("Ymd\THis", strtotime($startTime)). "\r\n" .
    'DTEND;TZID="Eastern Time":'.date("Ymd\THis", strtotime($endTime)). "\r\n" .
    'TRANSP:OPAQUE'. "\r\n" .
    'SEQUENCE:1'. "\r\n" .
    'SUMMARY:' . $subject . "\r\n" .
    'LOCATION:' . $location . "\r\n" .
    'CLASS:PUBLIC'. "\r\n" .
    'PRIORITY:5'. "\r\n" .
    'BEGIN:VALARM' . "\r\n" .
    'TRIGGER:-PT15M' . "\r\n" .
    'ACTION:DISPLAY' . "\r\n" .
    'DESCRIPTION:Reminder' . "\r\n" .
    'END:VALARM' . "\r\n" .
    'END:VEVENT'. "\r\n" .
    'END:VCALENDAR'. "\r\n";
    $message .= 'Content-Type: text/calendar;name="meeting.ics";method=REQUEST'."\n";
    $message .= "Content-Transfer-Encoding: 8bit\n\n";
    $message .= $ical;

    $mailsent = mail($to_address, $subject, $message, $headers);

    return ($mailsent)?(true):(false);
}

And here's a sample of what the function call might look like: 

$from_name = "webmaster";        
$from_address = "webmaster@example.com";        
$to_name = "Joseph";        
$to_address = "joe@example.com";        
$startTime = "11/12/2013 18:00:00";        
$endTime = "11/12/2013 19:00:00";        
$subject = "My Test Subject";        
$description = "My Awesome Description";        
$location = "Joe's House";
sendIcalEvent($from_name, $from_address, $to_name, $to_address, $startTime, $endTime, $subject, $description, $location);

Author: Joe Meyer
Date: January 2, 2014

Goal: Create a facebook app Deauthorization callback with the Yii Framework and Facebook PHP SDK

Difficulty: Medium

Prerequisites: A Good Understanding of the Yii Framework is Helpful, A good understanding of the Facebook PHP SDK

One of the most important things when setting up a web application with Facebook is setting up a deauthorization callback so that when someone remove's your application via the Facebook Application interface you can deal with that appropriately for your application.

In this example I will be demonstating how to set up a callback URL on http://test.exchangecore.com/facebook/deauthorize. This example is written using Yii 1.1 which can be downloaded at http://www.yiiframework.com. I have also added and configured the Yii Facebook PHP SDK to my environment to make use of the Facebook PHP SDK, downloadable from http://www.yiiframework.com/extension/facebook-opengraph/

Firstly we need to create a base64_url_decode function to decode the data. To do this I've added the following function to my FacebookController.php file. This could just as easily be added to a helper file if you plan to utilize this for other things.

private static function base64_url_decode($input) {
    return base64_decode(strtr($input, '-_', '+/'));
}

Next we'll create a static function in the FacebookController.php file (also could be moved into a helper class), that does all of the request processing sent by Facebook. This will return false if there is an error parsing the data and log the error to the yii application log.

private static function parseSignedRequest() {
    if (isset($_REQUEST['signed_request'])) {
        $signed_request = $_REQUEST['signed_request'];
        list($encoded_sig, $payload) = explode('.', $signed_request, 2);

        // decode the data
        $sig = self::base64_url_decode($encoded_sig);
        $data = json_decode(self::base64_url_decode($payload), true);

        if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') {
            Yii::log('Unknown algorithm. Expected HMAC-SHA256', 'error');
            return false;
        }

        // Adding the verification of the signed_request below
        $expected_sig = hash_hmac('sha256', $payload, Yii::app()->facebook->secret, $raw = true);
        if ($sig !== $expected_sig) {
            Yii::log('Bad Signed JSON signature!', 'error');
            return false;
        }

        return $data;
    } else {
        return false;
    }
}

Finally, we add our deauthorization code. in my example I simply use my users model to get the application user ID and then run my deauthorize command. Note that the parseSignedRequest() returns an array of information about the deauthorization from php.

public function actionDeauthorize(){
    $data = self::parseSignedRequest();
    if($data === false){
        //there was an error
        throw new CHttpException('500', 'There was a problem with the request format.');
    }else{
        //build your deauthroization stuff here
        $userID = Users::getIdByFbUserID($data['user_id']);
        Users::deauthhorize($userID);
    }
}

And that's it. Just make sure that you set up your URL callback in your facebook app like below:
Facebook Deauth Callback.png