The Surly Admin

Father, husband, IT Pro, cancer survivor

New Version: Employee Directory

I keep getting requests “Can I get this field?” or “Can I get that field?” for the Employee Directory script and honestly it wasn’t designed for that.  The header in the table has to be manually changed, and the JavaScript that does the button filtering has to be altered.  Not to mention the reverse engineering that has to occur because I don’t necessarily remember everything I did in the script 6 months ago!  So it was time to sit down with the script and see if there was something I could do about this.  Here’s what I came up with.

It’s a Complex Script

I struggled with just conceptualizing how I wanted to write this script.  The basic idea was I wanted the field to be dynamically selected by the user!  I wanted the button bar to be able to filter on different fields and I wanted to give the user the ability to pick and choose what Organization Units would be included in the directory.  The irony is this led me back to putting the capabilities my original VBScript had back into the Powershell version.  So much for simplifying!  I also wanted the ability to let you put in custom CSS, as well as an “open” area where you could put anything you wanted at all in.  This is actually something I’ve been doing all along with my own version of the script that I’m running at SeraCare but I always left it out of the public version.

But if you think about it that meant there was a lot of data that the script needs from YOU, and the question becomes how do you easily tell the script that information?  I thought about putting a giant hashtable at the front where you could enter $true and $false for the fields you want and this would work really well for selecting the fields you want but what about putting the fields in the order you want?  And this did nothing for the custom CSS and custom HTML.

I thought about an INI files, graphic interfaces, etc.  But finally I decided to go with a mixture of classic parameters and simple text files.  Why?  Partly because I didn’t want to extend the development time too far, and graphic interface would be new for me and would extend development quite extensively.  An INI wouldn’t take as much time but structuring it would be interesting to say the least.

Zones

I’m not going to go into my usual detail about how I constructed this script and spend more time just trying to document how to use the script.  The Employee Directory page is split into 4 zones, Heading, Button Bar, Title and Data Grid.  Like so:

empdirzones

Zone 1: Custom Heading

The Custom Heading has one parameter related to it and that’s the HTMLPath parameter.  By default this path will be the same path from which the script is run from.  To load a custom heading you must name a file “heading.html”.  Inside this simple text file is the custom HTML you want to include in your Employee Directory.  This can pretty much be any HTML fragment you want to put in here, just make sure to not have any , , , , etc tags in it or you’ll get some unexpected results.

Related to Zone 1 is the Custom CSS capability.  The script includes default CSS built-in, but this may not match your desired color scheme.  By placing a file called CSS.HTML in the same folder as specified by HTMLPath (again, the default is the same path where you run the script) the script will read that CSS in and use that instead of the CSS included in the script.  Again, this is a CSS fragment, so no

<!–
or tags (or closing tags), just the straight CSS.  The script uses the default table settings so you will need to include those in your CSS fragment.  Here is the default CSS you can base your custom CSS off of:
–>

form {
 margin: 0;
}
table {
 background:#D3E4E5;
 border:1px solid gray;
 border-collapse:collapse;
 color:#fff;
 font:normal 12px verdana, arial, helvetica, sans-serif;
 width:95%;
}
caption { border:1px solid #5C443A;
 color:#5C443A;
 font-weight:bold;
 padding:6px 4px 8px 0px;
 text-align:center;
}
td, th { color:#363636;
 padding:.4em;
}
tr { border:1px dotted gray;
}
thead th, tfoot th { background:#5C443A;
 color:#FFFFFF;
 padding:3px 10px 3px 10px;
 text-align:left;
 text-transform:uppercase;
}
tbody th, tbody td { text-align:left;
 vertical-align:top;
}
tbody tr:hover { background:#99BCBF;
 border:1px solid #03476F;
 color:#000000;
}

Zone 2: The Button Bar

The Employee Directory also has a button bar for filtering employee’s based on a specific criteria.  With this new version you can specify which field you want to key your button bar off of.  The parameter related to this is ButtonBy and you can designate one of three fields, Department, Location or Manager.  If you set ButtonBy to “Manager”, then the script will locate every unique Manager in the Manager field and create a button for each instance.  If you have a very large organization you probably don’t want to use this.  Also, the manager field in Active Directory must be properly set and maintained for this data to be useful.  See my Manager Maintenance module for help there!  You can also use the Department field which is keyed directly off of Active Directory, or a “Location” field.  This is defined by either setting the location in the Office field in Active Directory, or taking a default location when you set the LocationDefault parameter (more on that later).

This section also includes the search box where you can search for employee’s by typing in key information.  The search box searches the entire row allowing you to get very specific with your searches.

Zone 3: Title

One of the easiest zones!  This is a simple text title of what you want to call the Employee Directory.  You define this with the Title Parameter.

Zone 4: Data Grid

Here’s the real meat and potato’s of the script, and requires some of the more difficult parameter settings.  The first two parameters we need to be concerned about are SearchBy and DefaultLocation.  These 2 parameters have a one to one relationship with each other, so if you have 3 items in the SearchBy parameter you must have 3 items in the DefaultLocation parameter.

SearchBy: Enter in the text of the OU you want to target to appear in the Employee Directory.  Let’s say you have an OU structure like so:

OUStructureAnd you want your Employee Directory to contain users from Site 1 and Site 3, but not from Site 2.  To accomplish this you would set SearchBy like so:

$SearchBy = "OU=Site1","OU=Site3"

This will effectively exclude Site 2.  Now, since “OU=Site1” isn’t the friendliest of names, we want change that to give it something more friendly to the person using the webpage.  That’s where the LocationDefault parameter comes into play.  We would set our LocationDefault parameter like so:

$LocationDefault = "Boston","New York"

Remember that the Office field in Active Directory will override the LocationDefault, so any user with a blank Office field who is in “OU=Site1” will get “Boston” as their location.  “OU=Site3” will get “New York”.

The next field involved is the “SortBy” field.  You can sort of off the following fields:  LastName, FirstName, Ext, Department, Title, Fax, Email, Location, Link, Cell and Manager.

Fields

The single most important parameter is Fields, and this is where you define what fields you want to appear in your Employee Directory.  Here is the breakdown of the fields available and what they do:

Field Active Directory Field Description
Picture thumbnailPhoto Small 64×64 thumbnail of the employee photo.  Hover over the photo to see full sized version.
LastName Sn Last Name
FirstName GivenName First Name
LNLink SN and wwwHomePage Last Name as a hyperlink to the user’s web page
LNLinkPic SN and thumbnailPhoto Last Name as a hyperlink to the user’s photo
FNLink GivenName and wwwHomePage First Name as a hyperlink to the user’s web page
FNLinkPic GivenName and thumbnailPhoto First Name as a hyperlink to the user’s photo
Ext TelephoneNumber Telephone number
Department Department Department
Title Title Title
Fax FacsimileTelephoneNumber Fax number
Email Mail Email address as a “mailto:” hyperlink
Location PhysicalDeliveryOfficeName Office field, if blank will use the LocationDefault parameter
Link wwwHomePage Web page
Cell Mobile Mobile number
Manager Manager First and last name of the user’s manager, if it’s set
Description Description Description

You can choose any number of fields, and the order you put them in the Fields directory will be the order they are displayed.  For ease of entry the fields are comma separated and the script will take care of splitting them up.  Don’t pack too many fields into your list though, the employee directory is designed to fit on the current window and no bigger so things could get pretty cramped, pretty quickly!

Output and Pictures

The next parameter of not is the OutputPath parameter.  This is simply the path where you want to save the output from the script.  The script will create a file called EmployeeDirectory.HTML and this will be saved in the path you specify with this parameter.  Additionally the script will create a “Images” sub-folder where all of the employee photo’s will be stored.  If the folder does not exist the script will create it for you.

The last parameter is Refresh.  Normal behavior of this script is it will extract the photo from Active Directory only if it doesn’t already exist, which means the script will look into the Images sub-folder and if the photo is already there is will not do anything and move on to the next user.  If the photo isn’t there it will pull it out of Active Directory and save it.  This is fine except for 2 scenario’s:  if a photo gets changed and if the user leaves the company.  If a user leaves the photo will just be left in the Images folder, which isn’t horrible but will get cluttered eventually.  If the photo is changed in Active Directory, Employee Directory will never pick it up, which is a problem.

To solve this we use the Refresh parameter.  If this parameter is set then Employee Directory will delete all photo’s in the Images directory and then re-download them from Active Directory.

How to use the Script

I recommend you modify the Param section of Employee Directory to the settings you require, then run it on an hourly basis.  To make sure you get refreshed photo’s I recommend you run the script once after hours with the -Refresh parameter.  Here’s how I run it at work:

Trigger:  7am, run every hour for 12 hours (so 12 runs a day).
Program:  Powershell.exe
Argument:  -ExecutionPolicy Bypass c:\scripts\Out-EmployeeDirectory.ps1

Trigger: 11pm, run once
Program: Powershell.exe
Argument: -ExecutionPolicy Bypass c:\scripts\Out-EmployeeDirectory.ps1 -Refresh

Hopefully that gives you enough information to successfully implement the Employee Directory script.  As always you can post questions here on the blog, or in the Powershell forum over at Spiceworks.

Download Out-EmployeeDirectory here

Advertisement

May 16, 2013 - Posted by | PowerShell | , ,

121 Comments »

  1. Hi, been using v1 for a couple of months before I realised that v2 was out. Loving the multi-line button bar. Have you noticed that when you set $ButtonBy to department and have one or more users with no department (as we do because the users in question span multiple departments) that the script creates a blank button with id = ”” . Any tips on how to either suppress the blank button? Joe

    Comment by Joe | July 29, 2013 | Reply

    • Well, at first glance assign them a department 😉 I’ll look into it, we should be able to surpress blanks fairly easily!

      Comment by Martin9700 | July 29, 2013 | Reply

      • I’ve tried the former before, not worked 😦

        In v1 I used If ( $ Button.Department ) to filter out blanks, not able to get my head around how you are building the button row this time, looks like javascript, that pickles my head just a little…

        Comment by Joe | July 30, 2013

      • OK, I haven’t tested this but this change should do the trick. Change line 618 from
        $Buttons = $Data.Values | Select $ButtonBy -Unique | Sort $ButtonBy
        to
        $Buttons = $Data.Values | Where { $_ } | Select $ButtonBy -Unique | Sort $ButtonBy

        Comment by Martin9700 | July 30, 2013

      • Ahh, thanks for the code and pointer. Yours didn’t remove the button but I used a less elegant solution by adding another if loop { if ( $ Button . $ ButtonBy ) on or about line 627 and the blank button is gone.

        Thanks for your time and effort. Take care.

        Comment by Joe | July 30, 2013

  2. I know this script isn’t meant to have fields added but I really need the displayName field pulled in. I’ve gotten it to the point where the column shows up and if i put a string into it like “a” the a will show up for every user, however, when i replace the “a” with $($User.Properties.displayName) it pulls blanks. so basically:
    DisplayName =”a” – gives me a’s for every user so I know that works
    DisplayName = $($User.Properties.displayName) – is blank for every user
    so i tried:
    DisplayName = $($User.Properties.givenName) – to see if it would output the lastname again just as a test and it was blank as well.

    Any idea of what I could do? Has anyone else had luck adding AD fields?

    Comment by Mike Zbarsky | November 8, 2013 | Reply

    • Never mind I got it working! 🙂

      Comment by Mike Zbarsky | November 8, 2013 | Reply

  3. I’ve been trying to learn PS and I love this script from Spiceworks but I have a weird issue. It ran fine the 1st time I tried it under v2.0 but it is not picking up new accounts in AD and in one OU, I’ve added 10 people and only the original account is showing up. I deleted everything on the webserver and started clean with new images path and no .html and the same thing happened.

    I chunked the script into ISE and this is what it gacking on:

    PS C:\Documents and Settings\Administrator> D:\PwrShellScripts\EmployeeDirectory.ps1
    Cannot index into a null array.
    At D:\PwrShellScripts\EmployeeDirectory.ps1:175 char:47
    + { $HTMLPath = Split-Path ((Get-PSCallStack)[ <<<< 0].ScriptName)
    + CategoryInfo : InvalidOperation: (0:Int32) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

    I didn't modify that path in the parameters HTMLpath as I'm just trying to use the default output right now.

    This error seems to be looking for an array value but I don't understand what it would be. For a custom .html file and custom .css file, would the array be 0, 1? Do I just need to create blank files for this and could this error be why I'm missing the new AD accounts?

    Any help is very much appreciated.

    Comment by Mike | December 6, 2013 | Reply

    • The array is just what’s returned from Get-PSCallStack. The easiest way around this problem is to specify $Path. It’s funny, it’s not that easy to get the location that the script was launched from! I did test this with v2.0 and v3.0 of PowerShell, is it possible you’re running 1.0?

      Comment by Martin9700 | December 11, 2013 | Reply

  4. new to this stuff, getting the following error:

    Unexpected token ‘)’ in expression or statement
    At C:\scripts\Out-EmployeeDirectory.ps1:159 char 3
    + ) <<<
    [CmdletBinding()]
    Param (
    [string]$Title = “Henry Ford Village Employee Directory”,

    [string[]]$SearchOU = “OU=HFVillage users”),
    [String[]]$LocationDefault = (“HFVillage users”),

    [ValidateScript({Test-Path $_ -PathType Container})]
    [string]$OutputPath = “\\hfv-srv-0102\data\common”,

    [ValidateScript({Test-Path $_})]
    [string]$HTMLPath,

    [ValidatePattern(“Picture|LastName|FirstName|LNLink|LNLinkPic|FNLink|FNLinkPic|Ext|Department|Title|Fax|Email|Location|Link|Cell|Manager|Description”)]
    [string]$Fields = “Picture,LastName,FirstName,Title,Manager,Location,Ext,Cell,Fax,Email”,

    [ValidatePattern(“LastName|FirstName|Ext|Department|Title|Fax|Email|Location|Link|Cell|Manager”)]
    [string]$SortBy = “LastName”,

    [ValidatePattern(“Department|Location|Manager”)]
    [string]$ButtonBy = “Location”,

    [switch]$Refresh
    )

    Function Set-EscapeCharacters
    { Param (
    [string]$Field
    )
    $Field = $Field.Replace(“[“,”[”)
    }

    Comment by mike t | February 19, 2014 | Reply

    • Hmm… most likely it’s teh parenthesis you’ve added into the $SearchOU and $LocationDefault parameters:
      [string[]]$SearchOU = “OU=HFVillage users”),
      [String[]]$LocationDefault = (“HFVillage users”),

      Just remove those…

      Comment by Martin9700 | February 19, 2014 | Reply

      • One last thing. I want this to update every hour. Where and how do I do that? What is the syntax? I found the refresh param as shown below:

        [switch]$Refresh
        )

        Now what?
        Thanks in advance for your help!

        Comment by mike t | February 19, 2014

      • You have to run the script as a scheduled task, so just set a trigger to run once an hour. Once a day run it an extra time and use the REFRESH parameter which will delete all of the pictures and reload them from Active Directory (cleaning out any old ones, updating any that have been changed, etc).

        Comment by Martin9700 | February 19, 2014

  5. That did it! Thanks!

    Comment by mike t | February 19, 2014 | Reply

    • Just schedule it in task manager…

      Comment by mike | February 19, 2014 | Reply

  6. […] I wrote some time back, but that function was very limited.  Later came a new version of the Employee Directory script and this is where the ideas behind this script really began to germinate.  The problem with […]

    Pingback by ConvertTo-AdvHTML – New Advanced Function for HTML Reporting « The Surly Admin | February 27, 2014 | Reply

  7. Everything works as designed and I love it :). I have made some changes to add a few extra columns and removed a few. I am however struggling on getting the ipPhone attribute to populate. does anyone know of a limitation? I can get it to grab using vbs so I know that attribute is populated correctly in AD, but the powershell just isn’t working. All the other columns I added are working perfectly too.

    Comment by Michael | August 27, 2014 | Reply

    • Michael,
      What code are you using I added ipphone with no issues. You need to have it in 4 places:

      1)

      [ValidatePattern(“Picture|Status|LastName|FirstName|DisplayName|LNLink|LNLinkPic|FNLink|FNLinkPic|ipphone|Department|Title|Office|Email|Location|Link|Cell|Manager|Description”)]

      2)

      [ValidatePattern(“LastName|DisplayName|Status|FirstName|Ipphone|Department|Title|Office|Email|Location|Link|Cell|Manager”)]

      3) $PropertiesToLoad = “SamAccountName,useraccountcontrol,distinguishedname,GivenName,DisplayName,sn,Title,description,department,physicaldeliveryofficename,manager,TelephoneNumber,Mobile,ipphone,mail,thumbnailphoto,wwwhomepage,description”

      (this will be different depending on what columns you’ve added etc)

      4) Under the area with:

      #Now load the data
      $Object = New-Object PSObject -Property @{

      add:
      ippone = $($User.Properties.ipphone)

      Do you have it everywhere? If so post your code and I can take a look.

      Comment by Mike | August 27, 2014 | Reply

      • I have placed the code below. Currently the ipPhone attribute in AD holds the Extension number of the ipPhone so I kept the Ext name. It seems like it should work just fine. Is there any addon’s I need to make this power shell work? I am using PowerGUI as my editor and testing platform.

        1)
        [ValidatePattern(“LastName|FirstName|LNLink|LNLinkPic|FNLink|FNLinkPic|Ext|Department|Title|Email|Location|Link|Cell|Description|Phone”)]

        2)
        [ValidatePattern(“LastName|FirstName|Ext|Department|Title|Email|Location|Link|Cell|Phone”)]

        3)
        $PropertiesToLoad = “SamAccountName,useraccountcontrol,distinguishedname,GivenName,sn,Title,description,department,physicaldeliveryofficename,TelephoneNumber,Mobile,ipPhone,mail,thumbnailphoto,wwwhomepage,description,company”

        4)
        Ext = $($User.Properties.ipPhone)

        Comment by Michael | August 28, 2014

      • Michael,
        You shouldn’t need any addon’s. I actually have it as Ext as well and modified it when i posted here. Let’s go over it again as I have it this is everywhere where i have “Ext”

        [ValidatePattern(“Picture|Status|LastName|FirstName|DisplayName|LNLink|LNLinkPic|FNLink|FNLinkPic|Ext|Department|Title|Office|Email|Location|Link|Cell|Manager|Description”)]
        [string]$Fields = “Picture,Status,DisplayName,Title,Manager,Location,Ext,Office,Cell,Email”,

        [ValidatePattern(“LastName|DisplayName|Status|FirstName|Ext|Department|Title|Office|Email|Location|Link|Cell|Manager”)]
        [string]$SortBy = “DisplayName”,

        #Verify all field entries are good
        $Regex = “Picture|Status|LastName|FirstName|DisplayName|LNLink|LNLinkPic|FNLink|FNLinkPic|Ext|Department|Title|Office|Email|Location|Link|Cell|Manager|Description”

        I also wanted the extensions bold so I have the following in the set-grid function:
        “\[Ext\](.*?)”
        {
        If ($Matches[1])
        {
        $Line = $Line.Replace(“[Ext]$($Matches[1])”,”$($Matches[1])“)
        }
        Else
        { $Line = $Line.Replace(“[Ext]“,””)
        }
        } #End Extention

        and finally in the load the data:
        Ext = “[Ext]$($User.Properties.ipphone)”

        Can you verify all of that? If you don’t want the extensions bold skip the set-grid function piece and in the load data piece just do: Ext = $($User.Properties.ipPhone)

        Let me know how it goes

        Comment by Mike | August 28, 2014

  8. Michael,
    You shouldn’t need any addon’s. I actually have it as Ext as well and modified it when i posted here. Let’s go over it again as I have it this is everywhere where i have “Ext”

    [ValidatePattern(“Picture|Status|LastName|FirstName|DisplayName|LNLink|LNLinkPic|FNLink|FNLinkPic|Ext|Department|Title|Office|Email|Location|Link|Cell|Manager|Description”)]
    [string]$Fields = “Picture,Status,DisplayName,Title,Manager,Location,Ext,Office,Cell,Email”,

    [ValidatePattern(“LastName|DisplayName|Status|FirstName|Ext|Department|Title|Office|Email|Location|Link|Cell|Manager”)]
    [string]$SortBy = “DisplayName”,

    #Verify all field entries are good
    $Regex = “Picture|Status|LastName|FirstName|DisplayName|LNLink|LNLinkPic|FNLink|FNLinkPic|Ext|Department|Title|Office|Email|Location|Link|Cell|Manager|Description”

    I also wanted the extensions bold so I have the following in the set-grid function:
    “\[Ext\](.*?)”
    {
    If ($Matches[1])
    {
    $Line = $Line.Replace(“[Ext]$($Matches[1])”,”$($Matches[1])“)
    }
    Else
    { $Line = $Line.Replace(“[Ext]”,””)
    }
    } #End Extention

    and finally in the load the data:
    Ext = “[Ext]$($User.Properties.ipphone)”

    Can you verify all of that? If you don’t want the extensions bold skip the set-grid function piece and in the load data piece just do: Ext = $($User.Properties.ipPhone)

    Let me know how it goes

    Comment by Mike | August 28, 2014 | Reply

    • Sorry about the double post.

      Comment by Mike | August 28, 2014 | Reply

    • Unless I’m blind I just don’t’ see anything different. Below is my code again. I think there is something else because when I swapped out Cell = $($User.Properties.mobile) to Cell = $($User.Properties.ipPhone) as a test the cell went blank. It is like it can’t retrieve the ipPhone info. I even tried it from another computer with no luck.

      I tried running with Powershell and got the below error but it still continued and created the file like normal. I’m an expert at powershell so I’m not sure what is causing this.

      Cannot index into a null array.
      At c:\temp\employeedirectory.ps1:178 char47
      + { $HTMLPath = Split-Path ((Get-PSCallStack)[ <<<< 0].ScriptName)
      + CategoryInfo : InvalidOperation: (0:Int32) [], RuntimeException
      + FullyQualifiedErrorID : NullArray

      Code:
      [ValidatePattern("LastName|FirstName|LNLink|LNLinkPic|FNLink|FNLinkPic|Ext|Department|Title|Email|Location|Link|Cell|Description|Phone")]
      [string]$Fields = "LastName,FirstName,Title,Location,Phone,Ext,Cell,Email",

      [ValidatePattern("LastName|FirstName|Ext|Department|Title|Email|Location|Link|Cell|Phone")]
      [string]$SortBy = "LastName",

      $Regex = "LastName|FirstName|LNLink|LNLinkPic|FNLink|FNLinkPic|Ext|Department|Title|Email|Location|Link|Cell|Description|Phone"

      Ext = $($User.Properties.ipPhone)

      Comment by Michael | August 28, 2014 | Reply

      • It should say I’m NOT and expert.. Woops..

        Comment by Michael | August 28, 2014

      • Can you post your full script and i’ll try debugging it?

        Comment by Mike | August 28, 2014

      • Sorry forgot to check the box to notify em by email when you respond.

        Comment by Mike | August 28, 2014

      • Here is the complete code, stripped off the top part of comments.

        [CmdletBinding()]
        Param (
        [string]$Title = “Employee Directory”,

        [string[]]$SearchOU = (“OU=Auburn”),
        [String[]]$LocationDefault = (“Auburn”),

        [ValidateScript({Test-Path $_ -PathType Container})]
        [string]$OutputPath = “c:\temp”,

        [ValidateScript({Test-Path $_})]
        [string]$HTMLPath,

        [ValidatePattern(“LastName|FirstName|LNLink|LNLinkPic|FNLink|FNLinkPic|Ext|Department|Title|Email|Location|Link|Cell|Description|Phone”)]
        [string]$Fields = “LastName,FirstName,Title,Location,Phone,Ext,Cell,Email”,

        [ValidatePattern(“LastName|FirstName|Ext|Department|Title|Email|Location|Link|Cell|Phone”)]
        [string]$SortBy = “LastName”,

        [ValidatePattern(“Department|Location”)]
        [string]$ButtonBy = “Location”,

        [switch]$Refresh
        )

        Function Set-EscapeCharacters
        { Param (
        [string]$Field
        )
        $Field = $Field.Replace(“[“,”[”)
        }

        #Setup
        Write-Verbose “$(Get-Date): Script begins”
        $ImagesPath = “$OutputPath\images”

        #Check if paths exist
        If (-not $HTMLPath)
        { $HTMLPath = Split-Path ((Get-PSCallStack)[0].ScriptName)
        }
        If (-not (Test-Path $OutputPath -PathType Container))
        { Write-Error “`nPath $OutputPath does not exist, script cannot continue.”
        Exit
        }
        Else
        { If (-not (Test-Path $ImagesPath))
        { Write-Verbose “$(Get-Date): $ImagesPath not detected, attempting to create”
        Try {
        New-Item -Path $ImagesPath -ItemType Directory -ErrorAction Stop | Out-Null
        }
        Catch {
        Write-Error $Error[0]
        Exit
        }
        }
        }

        #Verify all field entries are good
        $Regex = “LastName|FirstName|LNLink|LNLinkPic|FNLink|FNLinkPic|Ext|Department|Title|Email|Location|Link|Cell|Description|Phone”
        ForEach ($Field in $Fields.Split(“,”))
        { If ($Field -notmatch $Regex)
        { Write-Error “Field: $Field, is not a valid field”
        Exit
        }
        }

        #Determine Filter Column
        If (-not ($ButtonBy -match “Department|Location”))
        { Write-Error “ButtonBy must be either Department, Location or Manager. Set to: $ButtonBy”
        Exit
        }
        Else
        { $FilterColumn = 0
        ForEach ($Column in $Fields.Split(“,”))
        { If ($Column -eq $ButtonBy)
        { $Found = $true
        Break
        }
        $FilterColumn ++
        }
        If (-not $Found)
        { Write-Error “ButtonBy parameter does not match a specified field”
        Exit
        }
        }

        #Verify Good Sort Field
        $Regex = $Fields.Replace(“,”,”|”) + “|LastName|FirstName”
        If ($SortBy -notmatch $Regex)
        { Write-Error “SortBy parameter does not match a specified field in `$Fields”
        Exit
        }

        #Change the Search parameter to Regex
        $SearchRegexOU = $SearchOU -join “|”
        $SearchRegexOU = $SearchRegexOU.Replace(“/”,”\/”) #Escape some likely characters
        $SearchRegexOU = $SearchRegexOU.Replace(“.”,”\.”)

        Write-Verbose “$(Get-Date): Search: $SearchRegexOU”
        Write-Verbose “$(Get-Date): Output Path: $OutputPath”
        Write-Verbose “$(Get-Date): Images Path: $ImagesPath”

        #Refresh images?
        If ($Refresh)
        { Write-Verbose “$(Get-Date): Full refresh requested, deleting old images…”
        Try {
        Remove-Item $ImagesPath\*.jpg -Force -ErrorAction Stop
        }
        Catch {
        Write-Error $Error[0]
        Exit
        }
        }

        #Custom heading?
        If (Test-Path $HTMLPath\Heading.HTML)
        { Write-Verbose “$(Get-Date): Custom heading detected $HTMLPath\Heading.HTML. Adding to Employee Directory”
        $HeadingHTML = Get-Content $HTMLPath\Heading.HTML
        }
        Else
        { Write-Verbose “$(Get-Date): Custom $HeadingPath\Heading.HTML not found, will not be included in Employee Directory”
        }

        #Custom CSS?
        If (Test-Path $HTMLPath\CSS.HTML)
        { Write-Verbose “$(Get-Date): Custom CSS detected $HTMLPath\CSS.HTML overriding default CSS”
        $CSSHTML = Get-Content $HTMLPath\CSS.HTML
        }
        Else
        { Write-Verbose “$(Get-Date): No Custom CSS detected, using default”
        #Define Default CSS
        $CSSHTML = @”

        form {
        margin: 0;
        }
        table {
        background:#ECECEC;
        border:1px solid gray;
        border-collapse:collapse;
        color:#fff;
        font:normal 12px verdana, arial, helvetica, sans-serif;
        width:95%;
        }
        caption { border:1px solid #003D79;
        color:#003D79;
        font-weight:bold;
        padding:6px 4px 8px 0px;
        text-align:center;
        }
        td, th { color:#363636;
        padding:.4em;
        }
        tr { border:1px dotted gray;
        }
        thead th, tfoot th { background:#003D79;
        color:#FFFFFF;
        padding:3px 10px 3px 10px;
        text-align:left;
        text-transform:uppercase;
        }
        tbody th, tbody td { text-align:left;
        vertical-align:top;
        }
        tbody tr:hover { background:#D4D4D4;
        border:1px solid #003D79;
        color:#000000;
        }
        “@ #End Default CSS
        } #End Custom or Default CSS

        #Additional CSS
        $CSSHTML += @”

        .styleHidePicture {
        position:absolute;
        visibility:hidden;
        }
        .styleShowPicture {
        position:absolute;
        visibility:visible;
        border:solid 7px Black;
        padding:1px;
        }

        “@

        $JSHTML = @”

        function filter (phrase, _id){
        var words = phrase.value.toLowerCase().split(” “);
        var table = document.getElementById(_id);
        var ele;
        for (var r = 1; r < table.rows.length; r++){
        ele = table.rows[r].innerHTML.replace(/]+>/g,””);
        var displayStyle = ‘none’;
        for (var i = 0; i =0)
        displayStyle = ”;
        else {
        displayStyle = ‘none’;
        break;
        }
        }
        table.rows[r].style.display = displayStyle;
        }
        }
        function filtbutton(phrase){
        var tableMain = document.getElementById(‘TableMain’);
        for(i=1;i<tableMain.rows.length;i++){
        ele = tableMain.rows[i].innerHTML.replace(/]+>/g,””);
        tableMain.rows[i].style.display = ”;}
        for(i=1;i<tableMain.rows.length;i++){
        ele = tableMain.rows[i].cells[$FilterColumn].innerHTML.replace(/]+>/g,””);
        ele = ele.replace(“&”,”\&”)
        if (ele != phrase && phrase != ”) {
        tableMain.rows[i].style.display = ‘none’;
        } else {
        }
        }
        }

        “@ #End JSHTML

        $HeaderHTML = @”

        $Title

        “@ #End HeaderHTML

        $EndHeaderHTML = @”

        “@ #End EndHeaderHTML

        $SearchHTML = @”
        Search:

        “@ #End SearchHTML

        $FooterHTML = @”

        “@ #End FooterHTML

        #Functions
        #region Functions
        Function Set-Grid {
        [CmdletBinding()]
        Param(
        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
        [object[]]$HTMLInput
        )
        Begin {
        $HTMLOutput = @()
        }
        Process {
        ForEach ($Line in $HTMLInput)
        {
        Switch -regex ($Line)
        { “\[image\](.*?)”
        { If ($Data[$Matches[1]].PicturePath)
        { $Line = $Line.Replace(“[image]$($Matches[1])”,”“)
        }
        Else
        { $Line = $Line.Replace(“[image]$($Matches[1])”,””)
        }
        } #End Image
        “\[email\](.*?)”
        { If ($Matches[1])
        { $Line = $Line.Replace(“[email]$($Matches[1])”,”$($Matches[1])“)
        }
        Else
        { $Line = $Line.Replace(“[email]”,””)
        }
        } #End Email
        “\[fnlinkpic\](.*?)”
        { If ($Data[$Matches[1]].FirstName)
        { $FN = $Data[$Matches[1]].FirstName
        }
        Else
        { $FN = “Link”
        }
        If ($Data[$Matches[1]].PicturePath)
        { $Line = $Line.Replace(“[fnlinkpic]$($Matches[1])”,”$FN“)
        }
        Else
        { $Line = $Line.Replace(“[fnlinkpic]$($Matches[1])”,$FN.Replace(“Link”,””))
        }
        } #End FirstName Link to Pic
        “\[lnlinkpic\](.*?)”
        { If ($Data[$Matches[1]].LastName)
        { $LN = $Data[$Matches[1]].LastName
        }
        Else
        { $LN = “Link”
        }
        If ($Data[$Matches[1]].PicturePath)
        { $Line = $Line.Replace(“[lnlinkpic]$($Matches[1])”,”$LN“)
        }
        Else
        { $Line = $Line.Replace(“[lnlinkpic]$($Matches[1])”,$LN.Replace(“Link”,””))
        }
        } #End LastName Link to Pic
        “\[fnlink\](.*?)”
        { $DN = $Matches[1]
        If ($Data[$DN].FirstName)
        { $FN = $Data[$DN].FirstName
        }
        Else
        { $FN = “Link”
        } #End If
        If ($Data[$DN].URL)
        { $Link = Set-URL $Data[$DN].URL
        $Line = $Line.Replace(“[fnlink]$DN”,”$FN“)
        }
        Else
        { $Line = $Line.Replace(“[fnlink]$($Matches[1])”,$FN.Replace(“Link”,””))
        } #End If
        } #End FirstName Link to Home Page
        “\[lnlink\](.*?)”
        { $DN = $Matches[1]
        If ($Data[$DN].LastName)
        { $LN = $Data[$DN].LastName
        }
        Else
        { $LN = “Link”
        } #End If
        If ($Data[$DN].URL)
        { $Link = Set-URL $Data[$DN].URL
        $Line = $Line.Replace(“[lnlink]$DN”,”$LN“)
        }
        Else
        { $Line = $Line.Replace(“[lnlink]$($Matches[1])”,$LN.Replace(“Link”,””))
        } #End If
        } #End LastName Link to Home Page
        “\[link\](.*?)”
        { $DN = $Matches[1]
        If ($Data[$DN].URL)
        { $Link = Set-URL $Data[$DN].URL
        $Line = $Line.Replace(“[link]$DN”,”Home Page“)
        }
        Else
        { $Line = $Line.Replace(“[link]$DN”,””)
        }
        } #End Link to Home Page
        } #End Switch
        $Line = $Line.Replace(“”,””)
        $Line = $Line.Replace(“”,””)
        $Line = $Line.Replace(“”,””)
        $Line = $Line.Replace(“”,”$Title”)
        $Line = $Line.Replace(“”,””)
        $Line = $Line.Replace(“”,””)
        $Line = $Line.Replace(“FirstName”,”First Name”)
        $Line = $Line.Replace(“FNLinkPic”,”First Name”)
        $Line = $Line.Replace(“FNLink”,”First Name”)
        $Line = $Line.Replace(“LastName”,”Last Name”)
        $Line = $Line.Replace(“LNLinkPic”,”Last Name”)
        $Line = $Line.Replace(“LNLink”,”Last Name”)
        $Line = $Line.Replace(“[“,”[”)
        $Line = $Line.Replace(“]”,”]”)
        $HTMLOutput += $Line
        } #End ForEach
        } #End Process
        End {
        Return $HTMLOutput
        } #End End
        } #End Set-Grid Function

        Function Set-URL
        { Param (
        [string]$URL
        )
        If ($URL -match “https?:\/\/”)
        { $Link = $URL
        }
        Else
        { If ($URL)
        { $Link = “http://$URL”
        }
        Else
        { $Link = $null
        }
        } #End If
        Return $Link
        } #End Set-URL Function
        #endregion

        #Get User Information load it into an object for later
        Write-Verbose “$(Get-Date): Gathering data from Active Directory”
        $Domain = New-Object System.DirectoryServices.DirectoryEntry(“”)
        $ADSearch = New-Object System.DirectoryServices.DirectorySearcher
        $ADSearch.SearchRoot = $Domain
        $ADSearch.SearchScope = “Subtree”
        $ADSearch.Filter = “(objectCategory=User)”
        $PropertiesToLoad = “SamAccountName,useraccountcontrol,distinguishedname,GivenName,sn,Title,description,department,physicaldeliveryofficename,TelephoneNumber,Mobile,ipPhone,mail,thumbnailphoto,wwwhomepage,description,company”
        ForEach ($Property in $($PropertiesToLoad.Split(“,”)))
        { $ADSearch.PropertiesToLoad.Add($Property) | Out-Null
        }
        $Users = $ADSearch.FindAll()

        $Data = @{}
        ForEach ($User in $Users)
        { #Filter out users who don’t fall within the search parameters
        If ($($User.Properties.distinguishedname) -notmatch $SearchRegexOU)
        { Continue
        }

        #Filter out users with the word ‘Exclude’ in the department field
        If ($($User.Properties.physicaldeliveryofficename))
        { If ($($User.Properties.physicaldeliveryofficename).ToUpper() -eq “EXCLUDE”)
        { Continue
        }
        }

        #Filter out any disabled users
        If ($($User.Properties.useraccountcontrol) -band 0x2)
        { Continue
        }

        #Retrieve the Picture
        $File = “$ImagesPath\$($User.Properties.samaccountname).jpg”
        If (-not (Test-Path $File))
        { If (($User.Properties.thumbnailphoto).Count)
        { Try {
        $User.Properties.thumbnailphoto | Set-Content -Path $File -Encoding Byte -ErrorAction Stop
        }
        Catch {
        Write-Error “Unable to save thumbnail to $ImagesPath, see above error. Aborting script.”
        Break
        }
        }
        Else
        { $File = $null
        }
        }

        #Now load the data
        $Object = New-Object PSObject -Property @{
        LastName = $($User.Properties.sn)
        FirstName = $($User.Properties.givenname)
        Title = $($User.Properties.title)
        Department = $($User.Properties.department)
        Location = $($User.Properties.physicaldeliveryofficename)
        Manager = $($User.Properties.manager)
        Phone = $($User.Properties.telephonenumber)
        Ext = $($User.Properties.ipPhone)
        Cell = $($User.Properties.mobile)
        Fax = $($User.Properties.facsimiletelephonenumber)
        Email = “[email]$($User.Properties.mail)”
        SamAccountName = $($User.Properties.samaccountname)
        Picture = “[image]$($User.Properties.distinguishedname)”
        PicturePath = $File
        URL = $($User.Properties.wwwhomepage)
        Link = “[link]$($User.Properties.distinguishedname)”
        FNLinkPic = “[fnlinkpic]$($User.Properties.distinguishedname)”
        LNLinkPic = “[lnlinkpic]$($User.Properties.distinguishedname)”
        FNLink = “[fnlink]$($User.Properties.distinguishedname)”
        LNLink = “[lnlink]$($User.Properties.distinguishedname)”
        Description = $($User.Properties.description)
        }
        $Data.Add($($User.Properties.distinguishedname),$Object)
        }

        #Populate Manager and Default Location
        ForEach ($User in $Data.Values)
        { #If ($User.Manager)
        #{ $User.Manager = “$($Data[$User.Manager].FirstName) $($Data[$User.Manager].LastName)”
        #}
        If ($User.Location)
        { $User.Location = $User.Location.Trim()
        }
        If ([string]::IsNullOrEmpty($User.Location))
        { For ($i = 0;$i -le ($SearchOU.Count – 1);$i++)
        { If ($User.Link -like “*$($SearchOU[$i])*”)
        { $User.Location = $LocationDefault[$i]
        Break
        }
        }
        }
        } #End ForEach

        #Build the Button row
        Write-Verbose “$(Get-Date): Building the HTML”
        $Buttons = $Data.Values | Where { $_ } | Select $ButtonBy -Unique | Sort $ButtonBy
        $ButtonHTML = @”

        “@
        $MaxButtons = 2
        ForEach ($Button in $Buttons)
        { If ($Button.$ButtonBy)
        { $MaxButtons ++
        $ButtonHTML += “`n”
        If ($MaxButtons -eq 9)
        { $MaxButtons = 1
        $ButtonHTML += “`n”
        }
        }
        }

        #Build the detail HTML
        $GridHTML = $Data.Values | Sort $SortBy | Select $($Fields.Split(“,”)) | ConvertTo-Html -Fragment | Set-Grid

        #Put it together and save
        $HeadHTML = $HeaderHTML + $CSSHTML + $EndHeaderHTML + $JSHTML
        $FullHTML = $HeadHTML + $HeadingHTML + $ButtonHTML + $SearchHTML + $GridHTML + $FooterHTML
        Write-Verbose “$(Get-Date): Saving HTML: $OutputPath\EmployeeDirectory.html”
        $FullHTML | Out-File $OutputPath\EmployeeDirectory.html
        & $OutputPath\EmployeeDirectory.html #Un-remark if you wish to have the page displayed automatically in your browser
        Write-Verbose “$(Get-Date): Script completed”

        Comment by Michael | August 28, 2014

  9. Can you post your full script and i’ll try debugging it?

    Comment by Mike | August 28, 2014 | Reply

    • I wasn’t sure which one to respond so I responded to the other one.

      Comment by Michael | August 28, 2014 | Reply

  10. Great job guys, I’m thrilled you’re just running with it! ipPhone might be blank if you’re not loading it in $ADSearch. Look for the $PropertiesToLoad variable and there’s a long list of properties in there, make sure ipPhone is in there. Then try $Ext again and see if it’s there.

    Comment by Martin9700 | August 28, 2014 | Reply

  11. Hi Martin, I do have it here.
    $PropertiesToLoad = “SamAccountName,useraccountcontrol,distinguishedname,GivenName,sn,Title,description,department,physicaldeliveryofficename,TelephoneNumber,Mobile,ipPhone,mail,thumbnailphoto,wwwhomepage,description,company”

    Maybe it is our AD causing the issue but if I can load it with the below script then it should load it in your script.

    Set objFSO = CreateObject(“Scripting.FileSystemObject”)
    Set objFile = objFSO.CreateTextFile(“output.txt”, True)
    Set objSysInfo = CreateObject(“ADSystemInfo”)
    strUser = objSysInfo.UserName

    Set objUser = GetObject(“LDAP://” & strUser)

    objFile.WriteLine “Home Phone: ” & objUser.homePhone
    objFile.WriteLine “Pager: ” & objUser.pager
    objFile.WriteLine “Mobile phone: ” & objUser.mobile
    objFile.WriteLine “IP Phone: ” & objUser.ipPhone
    objFile.WriteLine “Information: ” & objUser.info
    objFile.WriteLine “Fax Number: ” & objUser.facsimileTelephoneNumber

    Comment by Michael | August 28, 2014 | Reply

    • When you refer to the property in PowerShell, it’s one of the few times it’s case sensitive. After $User is loaded (my version it’s around line 629) you can check if the property loaded ok:

      $User.Properties.ipphone

      Has to be all lowercase on the property name.

      Comment by Martin9700 | August 29, 2014 | Reply

      • Ahhh Mike let me know if making it lowercase fixes it for you. Mine are lowercase and it works.

        Comment by Mike | August 29, 2014

    • Thank you guys, that was the Fix!

      Comment by Michael | August 29, 2014 | Reply

      • Great! ADSI is pretty goofy about that, it’s the only objects I’ve seen that show that behavior!

        Comment by Martin9700 | August 29, 2014

  12. We store the middle initial of users in our AD. Your Script appears to filter out any user with a middle initial. For example: John A. Doe. Since the full name is included in the DistinguishedName property, I am guessing that your script is filtering them out at line 559. Any idea how to fix this?

    Comment by John | October 29, 2014 | Reply

    • Can you post the line of code you are referring to? I’ve edited mine so much that my line numbers won’t match up.

      Comment by Mike | October 31, 2014 | Reply

      • { #Filter out users who don’t fall within the search parameters
        If ($($User.Properties.distinguishedname) -notmatch $SearchRegexOU)

        This is the OU filter and the problem is probably not here exactly. Since it is grabbing the DN and it includes the middle initial, something is tripping it up. I printed the $SearchRegexOU variable to verify it is correct.

        This could be related to the same problem John Freeman is having because we also recently started requiring middle initials this year.

        Comment by John | October 31, 2014

      • I removed the middle initial on a couple of users and they still did not show up. So this looks like the same problem John Freeman is having. It appears to be related to the date the AD account was created or how old the account is.

        Comment by John | October 31, 2014

      • John,
        That’s very odd. I have the script pulling in all users with no issues. if you want to we can do a quick screen sharing session and I can take a look to see if there’s anything odd.

        Comment by Mike | October 31, 2014

  13. I would love to use this script but I’m having an issue I can’t figure out. It does not return all users from active directory. Any users created in the last year just aren’t returned. ??

    Comment by John Freeman | October 30, 2014 | Reply

    • Do you have the OU they are in specified under [string[]]$SearchOU = ?
      in general it should pull everyone within any OU specified there unless they have “exclude” as their department… See if you can narrow it down some as to what OU’s aren’t being pulled.

      Comment by Mike | October 31, 2014 | Reply

      • That’s the weird part. It’s not missing entire OU’s, just certain users within an OU. OU named “CM” and only 2 of the seven users in that OU show up. I’ve commented out the filtering thinking it was a filter issue.

        Comment by John Freeman | October 31, 2014

      • Do all 5 of the users who don’t show up have middle initials? If that’s the case can you delete the middle initial from one and rerun the script to see if it picks him up? If it does let me know and I’ll see if i can figure out why.

        Comment by Mike | October 31, 2014

  14. I agree with other John above, it seems to be related to newer accounts. Both users that are showing up already had middle initials.

    Comment by John Freeman | October 31, 2014 | Reply

    • John,
      There has to be something within AD that’s different about them if they are in the same OU. Can you post screenshots of the general, address, account, profile, telephone, and org tabs from a user that is working and one that isn’t?

      Comment by Mike | October 31, 2014 | Reply

  15. I am using PowerShell 4.0

    To get this to work correctly and pull all ADUsers in the specified OU, I had to replace the DirectorySearcher (lines 545 – 554) with Get-ADUser:

    $Domain = New-Object System.DirectoryServices.DirectoryEntry(“”)
    $ADSearch = New-Object System.DirectoryServices.DirectorySearcher
    $ADSearch.SearchRoot = $Domain
    $ADSearch.SearchScope = “Subtree”
    $ADSearch.Filter = “(objectclass=user)”
    $PropertiesToLoad = “SamAccountName,useraccountcontrol,distinguishedname,GivenName,sn,Title,description,department,physicaldeliveryofficename,manager,TelephoneNumber,Mobile,facsimiletelephonenumber,mail,thumbnailphoto,wwwhomepage,description”
    ForEach ($Property in $($PropertiesToLoad.Split(“,”)))
    { $ADSearch.PropertiesToLoad.Add($Property) | Out-Null
    }
    $Users = $ADSearch.FindAll()

    with Get-ADUser:

    $Users = Get-ADUser -Filter {enabled -eq $true} -SearchBase $SearchRegexOU -Properties SamAccountName,useraccountcontrol,distinguishedname,GivenName,sn,Title,description,department,physicaldeliveryofficename,manager,TelephoneNumber,Mobile,facsimiletelephonenumber,mail,thumbnailphoto,wwwhomepage

    And replace all instances of $User.Properties. with $User.

    Comment by John | November 6, 2014 | Reply

    • And replace all instances of $User.Properties. with $User. (be sure to include the period at the end of $User).

      Comment by John | November 6, 2014 | Reply

      • So I replaced the directory searcher lines with the Get-ADUser. and I’m getting this in powershell:
        Get-ADUser : An empty SearchBase is only supported while connected to a GlobalCatalog.
        At C:\Users\jfreeman\Desktop\Create Employee Directory Web Page.ps1:548 char:10
        + $Users = Get-ADUser -Filter {enabled -eq $true} -SearchBase $SearchRegexOU -Prop …
        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo : InvalidArgument: (:) [Get-ADUser], ArgumentException
        + FullyQualifiedErrorId : ActiveDirectoryCmdlet:System.ArgumentException,Microsoft.ActiveDirectory.Management.Commands.GetADUser

        Comment by John Freeman | November 6, 2014

      • What is the value of $SearchRegexOU after you attempt to run the script? Also what version of PowerShell are you running?

        Comment by John | November 6, 2014

      • the value of $SearchRegexOU appears to be empty. I am using Power Shell 4.0

        Comment by John Freeman | November 6, 2014

      • At Line 141, did you set the OU in $SearchOU?

        As an example:

        [string[]]$SearchOU = (“OU=Office,OU=Users,DC=domain,DC=com”),

        The easiest way to get the OU path is to do a Get-ADUser on someone in the OU and look at their DistinguishedName.

        Comment by John | November 6, 2014

      • No errors now but nothing returned from AD either.:
        [string[]]$SearchOU = (“OU=City Users,DC=BrokenArrow,DC=Local”),

        DistinguishedName : CN=Flasch\, Krista,OU=City Manager,OU=City Users,DC=BrokenArrow,DC=Local

        Comment by John Freeman | November 6, 2014

      • Try removing these sections:
        #Filter out users who don’t fall within the search parameters
        If ($($User.Properties.distinguishedname) -notmatch $SearchRegexOU)
        { Continue
        }
        #Filter out any disabled users
        If ($($User.Properties.useraccountcontrol) -band 0x2)
        { Continue
        }

        The Get-ADUser command if performing these steps with the -Filter {enabled -eq $true} and -SearchBase $SearchRegexOU

        Comment by John | November 11, 2014

    • I also had the problem of a few staff missing. We use middle initial occasionally when a username is already in use. I implemented this change which brought those missing ones back in, but our issue is we have users in two different OU’s. Excuse my ignorance, but is it not possible to search for two different OU’s in the same script? From what I have read, you can’t. Any help would be much appreciated.

      Comment by Sandy H | November 10, 2014 | Reply

      • This may work but I have not tested:

        Set the $SearchOU an dinclude the two OUs:
        [string[]]$SearchOU = (‘OU=OU 1,DC=domain,DC=com’,’OU=OU 2,DC=domain,DC=com’),

        Change the $Users = Get-ADUSer line I specified previously to:
        $Users = $SearchRegexOU | ForEach { Get-ADUser -Filter {enabled -eq $true} -SearchBase $_ -Properties SamAccountName,useraccountcontrol,distinguishedname,GivenName,sn,Title,description,department,physicaldeliveryofficename,manager,TelephoneNumber,Mobile,facsimiletelephonenumber,mail,thumbnailphoto,wwwhomepage}

        You will need to remove the lines that contain:
        If ($($User.Properties.distinguishedname) -notmatch $SearchRegexOU)
        { Continue
        }
        #Filter out any disabled users
        If ($($User.Properties.useraccountcontrol) -band 0x2)
        { Continue
        }

        Comment by John | November 11, 2014

    • Hi John,

      Thanks for the scripts, it works perfectly with all users pulling out from the AC. However, it can only work with one specified OU. Is there any way that I can add another OU in the list. Thanks.

      Comment by thang | November 10, 2014 | Reply

  16. hello Martin. Thank you for the script because is awesome. My problem is that not show the picture in the web page. I add the field Picture and show this but not show the user’s image. The image saved in the folder “images” correctly. Help me please because is very important for me.
    Thank you and sorry for my english

    Comment by Nacho Gonzalez Rodriguez | March 18, 2015 | Reply

  17. […] to help support all the scripts I’ve written.  In fact, if you’re subscribed to the Employee Directory post, you’ve seen the back and forth going on there!  I feel really bad that I haven’t […]

    Pingback by Opening up my Scripts « The Surly Admin | April 14, 2015 | Reply

  18. Hi,

    I’ve managed to get Multiple OU searching by using Get-ADUser. This means that you will have no missing users!

    You need to set:

    [string[]]$SearchOU = (“OU=SiteA,OU=domain,OU=Domain,OU=com,”OU=com”,”OU=SiteB,OU=domain,OU=Domain,OU=com,”OU=com”)

    Then, comment out everything what will use Directory searcher, and replace it with:

    $Users = $SearchOU | % { Get-ADUser -Filter {enabled -eq $true} -SearchBase “$_” -Properties SamAccountName,useraccountcontrol,distinguishedname,GivenName,sn,Title,description,department,physicaldeliveryofficename,manager,TelephoneNumber,Mobile,facsimiletelephonenumber,mail,thumbnailphoto,wwwhomepage}

    Then, find and replace every instance of $user.properties. with $user. ( The dots are important).

    There was a similar post about looping through each OU, but it didn’t work as the $searchRegexOU interprets it as a full string, as opposed to an array. Doing it this way does it in an array one by one, thus going through all the OU’s you specify.

    Comment by Josh S | June 24, 2015 | Reply

    • I originally used Get-ADUser, but what it couldn’t get was Contacts, which I wanted to include the the user directory. If that’s not what you’re after then that’s a great solution!

      Comment by Martin9700 | June 24, 2015 | Reply

      • Ahhh – did you consider using Get-ADObject and appending it to the $users var?

        Get-ADObject -LDAPFilter “objectClass=Contact”

        It is very strange though how it doesn’t find everybody using the directory searcher method, when I was looking I couldn’t find anything obvious as to why it wouldn’t work.

        Anyway – thank you for this – it has been a great help!

        Comment by Josh S | June 25, 2015

      • I did, and as I remember I wasn’t able to get it to work the way I wanted. There was also the consideration that I wanted the script to be as independent as possible so not relying on RSAT was a big bonus.

        Comment by Martin9700 | June 25, 2015

  19. I seem to be having the same issue as reported above as well, some users aren’t being picked up in an OU where other users are being picked up. As well as some OU’s just being skipped entirely. Anything I can try to fix it without re-writing the whole script?

    Comment by Brendan | July 23, 2015 | Reply

  20. Hi,

    Firstly, thank you for providing such a useful script for free. Very helpful. I have tailored the script to show just 4 columns; DisplayName, Telephone, Mobile and jobTitle. I am planning to replace a static PDF telephone list used internally. The script works fine, but many people print the list (we only have about 60 entries). When printed the column sizes show the mobile column as really wide and the jobTitle column quite narrow, relative to the information they contain. The list is also spilling over 3 pages, whereas the PDF prints to 2.

    What are the best options for dealing with this? I could fix the column widths but I like the idea the table dynamically resizes on window size. I wonder if the script could be adapted to also output a PDF or Word version of the table which would look neat and be easily printed.

    Thanks in advance for your suggestions.

    Comment by Michael | October 13, 2015 | Reply

  21. Hi All
    I have a problem, i think similar to others maybe, where newer AD accounts dont show, i cant see anything dirrerent in any of the accounts.
    Users in the same OU, only say 3 out of 5 appear! I cant see any difference in attribute editor for the users who dont and those that do, could you advise on this please?

    Cheers!

    Comment by James Muehlnickel | October 15, 2015 | Reply

  22. Thanks for a really nice script.
    I just have some issues with some pictures that won’t load into the html page. The picture is pulled from the AD and put into the folder, so far so good.
    But some users pictures is not shown. My conclusion so far is that pictures is now shown for users that have “special chars” in their name.
    We have nordic users that have letters like åäöø and so on. Even names the é in it dose not show pics.

    Here is two users that both have picture in AD and picture is pulled and put into the \images folder and how they are presented in the html file.

    ElgstrømMikkelMalmö+46123456789xxxxxxxx.xxMikkel.Elgstrom@xxxxxxxx.xxMana Ger

    EngholmNicholasMalmö+46123456789xxxxxxxx.xxne@xxxxxxxx.xxMikkel Elgstrøm

    Any Idea about this?

    Comment by Fredde k | October 20, 2015 | Reply

    • Ops.. it did not show the code right.. =/

      here is a link to a pic of it https://gyazo.com/6a4e20d5d707d5e9e06c7ece7fec31aa

      Comment by Fredde k | October 20, 2015 | Reply

      • Really Great Script, Overall works really well.
        I am also having problems with it displaying some photos. The script pulls the photo from AD and puts into the images folder no problem.
        At first I thought it was nordic characters as we have several users with nordic characters who were not displaying correctly. Looking closer that is not necessarily the case.
        Any user who has got some irregular character (&, æ, ñ etc) in the distinguished name, the html for the image is not being created. I renamed an ou with an ‘&’ in the name to ‘And’, all worked fine.

        Comment by Dom S | December 15, 2015

      • Hi Dom.
        Was that OU in the scope of where the script was looking or was it in AD in general?
        I have no special charater in any OU so i’m still stucked. the only one is blankspace but that is not in any OU i have add in the script to look at.

        Comment by Fredde k | December 16, 2015

      • Yes it was in the search scope. It turned out we only had 1 ou with an ‘&’ so I have just left the change to ‘and’.

        For our users with some Nordic characters I put the following:
        $User.Properties.distinguishedname = $User.Properties.distinguishedname.replace(“‘”,”'”)
        $User.Properties.distinguishedname = $User.Properties.distinguishedname.replace(“ñ”,”ñ”)
        $User.Properties.distinguishedname = $User.Properties.distinguishedname.replace(“å”,”å”)
        $User.Properties.distinguishedname = $User.Properties.distinguishedname.replace(“ø”,”ø”)
        $User.Properties.distinguishedname = $User.Properties.distinguishedname.replace(“æ”,”æ”)
        $User.Properties.distinguishedname = $User.Properties.distinguishedname.replace(“á”,”á”)
        before
        $Data.Add($($User.Properties.samaccountname),$Object) – line 624

        Maybe not the most elegant, I’m not so good with coding / scripting but this is working for me.

        Comment by Dom S | December 16, 2015

      • Meant before :
        $Data.Add($($User.Properties.distinguishedname),$Object)

        Comment by Dom S | December 16, 2015

      • hello,

        I also have got users with special characters and the pictures are not there 😦
        I’ve tried the .replace tricke but it did not work.
        Could it be an UTF8 issue?

        Comment by dalleins@skysoft-atm.com | December 8, 2016

      • Have no idea.
        have from time to time give it some look and try some of the suggestions without luck.
        If you stumble on a solution please let me know. and I do the same.

        Comment by Fredrik Karlsson | December 8, 2016

      • hi.

        I’ve found a solution this morning after searching during hours (thanks to your initial “replace” idea)

        in the Set-Griud Function, I’ve added this :
        #ndalleinne replace uncorrectly encoded special characters
        $Matches[1] = $($Matches[1]).Replace(“ï”,”`ï”)
        $Line = $Line.Replace(“ï”,”`ï”)
        $Matches[1] = $($Matches[1]).Replace(“é”,”`é”)
        $Line = $Line.Replace(“é”,”`é”)
        $Matches[1] = $($Matches[1]).Replace(“à”,”`à”)
        $Line = $Line.Replace(“à”,”`à”)
        $Matches[1] = $($Matches[1]).Replace(“è”,”`è”)
        $Line = $Line.Replace(“è”,”`è”)

        just after this :
        ForEach ($Line in $HTMLInput)
        { Switch -regex ($Line)
        { “\[image\](.*?)”
        {

        This table helped me a lot : http://ascii-table.com/special-chars.php as the special characters were shown as &#239 etc….

        BTW, I’ve also replaced $User.Properties.physicaldeliveryofficename by $User.Properties.department to let the Exclude feature working with the department field.

        Now I’ve got a perfectly running solution!
        Thanks

        Comment by dalleins@skysoft-atm.com | December 8, 2016

      • I just see that this website is thansforming my \&#239 by ï……..

        Comment by dalleins@skysoft-atm.com | December 8, 2016

      • here is the code how it should be read and used then:
        $Matches[1] = $($Matches[1]).Replace(“\ï”,”`ï”)
        $Line = $Line.Replace(“\ï”,”`ï”)
        $Matches[1] = $($Matches[1]).Replace(“\é”,”`é”)
        $Line = $Line.Replace(“\é”,”`é”)
        $Matches[1] = $($Matches[1]).Replace(“\à”,”`à”)
        $Line = $Line.Replace(“\à”,”`à”)
        $Matches[1] = $($Matches[1]).Replace(“\è”,”`è”)
        $Line = $Line.Replace(“\è”,”`è”)

        I understand better why my first “replace” attempts did not work

        Comment by dalleins@skysoft-atm.com | December 8, 2016

      • once again, the website transformed my input, third try.
        in the following source code, replace :
        – blablabla by &#239
        – bliblibli by &#233
        – blobloblo by &#224
        – blublublu by &#232
        and use this quote : ”
        $Matches[1] = $($Matches[1]).Replace(“blablabla;”,”`ï”)
        $Line = $Line.Replace(“blablabla;”,”`ï”)
        $Matches[1] = $($Matches[1]).Replace(“bliblibli;”,”`é”)
        $Line = $Line.Replace(“bliblibli;”,”`é”)
        $Matches[1] = $($Matches[1]).Replace(“blobloblo;”,”`à”)
        $Line = $Line.Replace(“blobloblo;”,”`à”)
        $Matches[1] = $($Matches[1]).Replace(“blublublu;”,”`è”)
        $Line = $Line.Replace(“blublublu;”,”`è”)

        Comment by dalleins@skysoft-atm.com | December 8, 2016

      • Thanks @dalleins That did the trick
        It’s working perfectly now.
        Any solution is easy when you see it 🙂

        Comment by Fredrik Karlsson | December 9, 2016

  23. Hi again,

    I am revisiting the need to print the HTML. I think I can solve the problem by manually specifying the widths of specific columns, but I would need to create custom CSS in order to achieve this. Most of the tutorials online for CSS table elements show standard HTML content using classes to identify a row. I believe your HTML is generated from the Set-Grid function. How do I know which columns or rows to target with the CSS when using the Grid function?

    Many thanks.

    Comment by Michael | November 5, 2015 | Reply

    • So I’ve done a lot of customization to the base script for different uses. I doubt you just want the html as is printed with pictures and all that so are there certain columns you want to print? In one of my iterations of modifying the script i actually have it export as a pdf for those that want to print. If you give me some more details on what you are trying to accomplish I can help you out.

      As far as your actual question you can target rows with css there are a few different options:
      Even/Odd: http://www.w3.org/Style/Examples/007/evenodd.en.html
      Nth: http://www.w3schools.com/cssref/sel_nth-child.asp
      Etc if you give us some more details on what you are trying to accomplish we can help you out more.

      Mike

      Comment by Mike | November 5, 2015 | Reply

      • The way I got around the problem was just another script that put all of the information into an Excel spreadsheet.

        Comment by Martin9700 | November 5, 2015

      • Thanks Mike.

        To explain, I only have 5 columns in my HTML (DisplayName, Initials, Extension, Cell, Title – in that order).

        In the built-in CSS, if I use “table-layout:auto” I get each column fitting to the largest value in any row. The table looks fine on screen, but when printed the DisplayName and Title columsn are wrapped which makes my address list print to around 3 pages.

        If I use “table-layout:fixed” instead, I get evenly spaced columns on screen and a similar problem to above when printing.

        I did experiment with using fixed table layout, but also…

        td, th { color:#853D6B;
        padding:.2em;
        width: 300px;
        #overflow: hidden;
        }

        The use of the width property makes all the columns 300px, which looks fine on screen and much better when printed. If What I would like is to specify…

        DisplayName – 300px
        Initials – best fit
        Extension – best fit
        Cell: 200px (and wrap when longer)
        Title – 600px

        …or thereabouts.

        The ability to export as PDF would be excellent by the way.

        Thanks for the tip about the odd/even rows – I just added that to the default CSS and it’s looking great.

        Comment by Michael | November 5, 2015

      • So if you export to pdf none of the css works obviously since that’s for html. Here is what I did:

        for your gridhtml line change it to export to csv:
        $GridHTML = $Data.Values | Sort $SortBy | Select $($Fields.Split(“,”))
        $GridHTML = $GridHTML | ConvertTo-Csv | Set-Grid
        $GridHTML | Out-File $OutputPath\EmployeeDirectoryDoc.csv

        Then convert the csv to a word/pdf:
        #Convert to Doc

        [ref]$SaveFormat = “microsoft.office.interop.word.WdSaveFormat” -as [type]
        $users = Import-Csv $OutputPath\EmployeeDirectorydoc.csv
        $path = “****OUTPUT PATH FOR WORD DOC****”
        $pathpdf = “****OUTPUT PATH FOR PDF****”
        $Number_Of_Rows = ($users.Count +1)
        $Number_Of_Columns = ($users | gm -MemberType NoteProperty).count
        $x = 2
        $Word = New-Object -ComObject word.application
        $Word.Visible = $false
        $Doc = $Word.Documents.Add()

        #Add Logo to header
        $Section = $Doc.Sections.Item(1);
        $Header = $Section.Headers.Item(1);
        $Header.Range.InlineSHapes.AddPicture(‘****LOCATION OF LOGO****’)

        #Add Table
        $Range = $Doc.Range()
        $Doc.Tables.Add($Range,$Number_Of_Rows,$Number_Of_Columns) | Out-Null
        $Table = $Doc.Tables.item(1)
        $Table.cell(1,1).range.Bold=1
        $Table.Cell(1,1).Range.Text = “First Name”
        $Table.cell(1,2).range.Bold=1
        $Table.Cell(1,2).Range.Text = “Last Name”
        $Table.cell(1,3).range.Bold=1
        $Table.Cell(1,3).Range.Text = “Location”
        $Table.cell(1,4).range.Bold=1
        $Table.Cell(1,4).Range.Text = “Extension”
        $Table.cell(1,5).range.Bold=1
        $Table.Cell(1,5).Range.Text = “Phone”
        Foreach($t in $users)
        {
        $Table.Cell($x,1).Range.Text = $t.FirstName
        $Table.Cell($x,2).Range.Text= $t.LastName
        $Table.Cell($x,3).Range.Text=$t.Location
        $Table.Cell($x,4).Range.Text=$t.Ext
        $Table.Cell($x,5).Range.Text=$t.Office
        $x++
        }
        #This colors rows 4,5,6,7,8 in red and makes them italic (I needed this for execs)
        ForEach ($r in 4,5,6,7,8)
        {
        ForEach ($c in 1,2,3,4,5)
        {
        $Table.Cell($r,$c).Range.Italic = 1
        $Table.Cell($r,$c).Range.Font.Color=”wdColorRed”
        $c++
        }
        $r++
        }
        $Table.cell(4,1).range.italic=1

        $Table.Style = “Table Grid”
        $Table.Columns.AutoFit()
        $doc.saveas([ref] $path, [ref]$SaveFormat::wdFormatDocumentDefault)
        $doc.saveas([ref] $pathpdf, [ref] 17)
        $doc.close()
        $word.quit()
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($doc) | Out-Null

        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($word) | Out-Null

        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($range) | Out-Null

        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($table) | Out-Null

        Remove-Variable Doc,Word, range, table

        [gc]::collect()

        [gc]::WaitForPendingFinalizers()

        Let me know if you have questions/if this makes sense

        Comment by Mike | November 5, 2015

      • Thanks very much for the code. I have pasted it into my script and replaced the GridHTML section as you specified. When running the script I get the error

        PS C:\scripts> C:\scripts\HTMLVersion – PDF.ps1
        Argument: ‘1’ should not be a System.Management.Automation.PSReference. Do not
        use [ref].
        At C:\scripts\HTMLVersion – PDF.ps1:695 char:12
        + $doc.saveas <<<< ([ref] $pathpdf, [ref] 17)
        + CategoryInfo : NotSpecified: (:) [], MethodException
        + FullyQualifiedErrorId : RefArgumentToNonRefParameterMsg

        It seems to be failing at $doc.saveas (I have commented out $doc.save as I don't need a Word version of this, just a PDF). Not sure what's wrong with the $pathpdf variable. I have tried

        $pathpdf = "$OutputPath\dir.pdf" ($OutPutPath is already a variable elsewhere and points to C:\users\michael\output)
        $pathpdf= "C:\users\michael\output\dir.pdf"

        I have tried with and without the quotes and with and without specifying the file name (dir.pdf).

        Saying all that I would prefer to fix just the issue with the table by using CSS if possible. Potentially having a button on the directory page called "printer friendly" which renders the same HTML but with a different CSS (smaller font, narrow columns etc).

        Any help if greatly appreciated.

        Comment by Michael | November 6, 2015

      • Mike,
        For it to work you need to have word installed on the machine where the script runs. Let me know if it still doesn’t work after word is installed.

        As for the css portion:
        Tell me more about your issue and what you are trying to achieve. I know you implemented the even/odd css. Are you trying to set a defined width to the columns or what else are you trying to achieve?

        Mike

        Comment by Mike | November 6, 2015

      • Hi,

        Thanks again – makes sense I need Word on the machine the script is running on. However, this just makes me want an HTML-only solution even more.

        My preference is to have a web page that looks good when printing – so I assume I need two CSS files – one for web viewing and one for “printer-friendly” version.

        The Web CSS would specify the following table for my 5 column headings

        DisplayName – 300px
        Initials – best fit
        Extension – best fit
        Cell: 200px (and wrap when longer)
        Title – 600px

        The printer-friendly CSS would be similar but include a smaller text font.

        I’m just not sure how to write my CSS.

        Thanks.

        Comment by Mike | November 11, 2015

      • Mike,
        So this can be done somewhat easily.

        First in your tag you need to assign a class to it. So make it

        Then in the css:
        .cols5 col:nth-child(1){width:300px;}
        .cols5 col:nth-child(2){width:auto;}
        .cols5 col:nth-child(3){width:auto;}
        .cols5 col:nth-child(3){width:200px;}
        .cols5 col:nth-child(3){width:600px;}

        Hope that helps. It should word-wrap by default

        Comment by Mike | November 11, 2015

      • Hi,

        Thanks again for your help – it is very much appreciated.

        However, I’m afraid I’m getting lost here. I have pasted my CSS below – where do I add the new lines you suggested?

        Also, where do I add the tag in the HTML?

        Apologies for the simple questions.

        Comment by Mike | November 11, 2015

      • I’m not seeing your css… as far as the html you should have a line in the script that looks like: $Line = $Line.Replace(“”,””)

        Change it to: $Line = $Line.Replace(“”,””)

        As far as the css it would look like this:
        table.cols5 col:nth-child(1){width:300px;}
        table.cols5 col:nth-child(2){width:auto;}
        table.cols5 col:nth-child(3){width:auto;}
        table.cols5 col:nth-child(3){width:200px;}
        table.cols5 col:nth-child(3){width:600px;}

        Comment by Mike | November 11, 2015

      • Hi,

        Thanks. I neglected to actually paste my CSS last time, so here it is, below. Regarding the line you asked me to find – it looks identical to the line you asked me to replace it with…!? Can you check again please?

        form {
        margin: 0;
        }
        table {
        background:#F2F2F2;
        border:1px solid;
        border-collapse:collapse;
        color:#853D6B;
        font:normal 13px verdana, arial, helvetica, sans-serif;
        width:100%;
        table-layout:fixed;
        }
        caption { border:1px solid #853D6B;
        color:#853D6B;
        font-weight:bold;
        padding:4px 4px 4px 0px;
        text-align:center;
        }
        td, th { color:#853D6B;
        padding:.2em;
        width: 300px;
        #overflow: hidden;
        }
        tr { border:1px dotted gray;
        }
        tr:nth-child(odd) {
        background: #F6F6F6
        }
        thead th, tfoot th { background:#853D6B;
        color:#FFFFFF;
        padding:3px 0px 3px 3px;
        text-align:left;
        text-transform:uppercase;
        }
        tbody th, tbody td { text-align:left;
        vertical-align:top;
        }

        Comment by Mike | November 11, 2015

      • PS: I have lots of these lines in the code..

        $Line = $Line.Replace(“”,””)
        $Line = $Line.Replace(“”,””)
        $Line = $Line.Replace(“”,””)
        $Line = $Line.Replace(“”,”$Title”)
        $Line = $Line.Replace(“”,””)
        $Line = $Line.Replace(“”,””)
        $Line = $Line.Replace(“FirstName”,”First Name”)
        $Line = $Line.Replace(“FNLinkPic”,”First Name”)
        $Line = $Line.Replace(“FNLink”,”First Name”)
        $Line = $Line.Replace(“LastName”,”Last Name”)
        $Line = $Line.Replace(“LNLinkPic”,”Last Name”)
        $Line = $Line.Replace(“LNLink”,”Last Name”)
        $Line = $Line.Replace(“Cell”,”Mobile”)
        $Line = $Line.Replace(“DisplayName”,”Name”)
        $Line = $Line.Replace(“ipPhone”,”Extension”)
        $Line = $Line.Replace(“Title”,”Job Title”)
        $Line = $Line.Replace(“[“,”[”)
        $Line = $Line.Replace(“]”,”]”)

        Comment by Mike | November 11, 2015

      • lol the browser is parsing the html tags which is why it came out with just commas.
        I took out the brackets from around table and table id=’TableMain’ so that is showed up in here
        Here is the like you want: $Line = $Line.Replace(“table”,”table id=’TableMain'”)

        Change that line to: $Line = $Line.Replace(“table”,”table id=’TableMain’ class=’cols5′”)
        and put brackets around table and the second portion as well

        Comment by Mike | November 11, 2015

      • Ok, thanks. I think i have that bit sorted.

        However, when I run the scrip the table looks the same (all columns evenly spaced). I think the cols5 part is getting ignore. Does it matter where in the CSS I have those lines? My CSS is below

        form {
        margin: 0;
        }
        table {
        background:#F2F2F2;
        border:1px solid;
        border-collapse:collapse;
        color:#853D6B;
        font:normal 13px verdana, arial, helvetica, sans-serif;
        width:100%;
        table-layout:fixed;
        }
        caption { border:1px solid #853D6B;
        color:#853D6B;
        font-weight:bold;
        padding:4px 4px 4px 0px;
        text-align:center;
        }
        td, th { color:#853D6B;
        padding:.2em;
        width: 300px;
        #overflow: hidden;
        }
        tr { border:1px dotted gray;
        }
        tr:nth-child(odd) {
        background: #F6F6F6
        }
        thead th, tfoot th { background:#853D6B;
        color:#FFFFFF;
        padding:3px 0px 3px 3px;
        text-align:left;
        text-transform:uppercase;
        }
        tbody th, tbody td { text-align:left;
        vertical-align:top;
        }

        .cols5 col:nth-child(1){width:300px;}
        .cols5 col:nth-child(2){width:auto;}
        .cols5 col:nth-child(3){width:auto;}
        .cols5 col:nth-child(3){width:200px;}
        .cols5 col:nth-child(3){width:600px;}

        Comment by Mike | November 11, 2015

      • Sorry i forgot the table tag at the start
        table.cols5 col:nth-child(1){width:300px;}
        table.cols5 col:nth-child(2){width:auto;}
        table.cols5 col:nth-child(3){width:auto;}
        table.cols5 col:nth-child(3){width:200px;}
        table.cols5 col:nth-child(3){width:600px;}

        Comment by Mike | November 11, 2015

      • Thanks, but it’s not working. The my CSS file is definitely getting queried and I can prove it if I change the background color

        As mentioned, does it matter where your lines go in the CSS file? I currently have the lines sitting at the end of the CSS.

        Comment by Mike | November 11, 2015

      • hrm that should work….oh get rid of this line: table-layout:fixed I think that should fix it.. if it doesn’t fix it is there a way you can take the html page and put it somewhere public for me to see?

        Comment by Mike | November 11, 2015

      • Commenting out the table-layout:fixed doesn’t help.

        I have saved the top half of the resultant HTML page below – hopefully this link works and you can pinpoint where I’m going wrong.

        Thanks again.

        https://www.dropbox.com/s/y19bzpi2kq09nqj/result.html?dl=0

        Comment by Mike | November 11, 2015

      • it’s showing up empty on dropbox. 0 KB. Try again?

        Comment by Mike | November 11, 2015

      • Try again please, should be correct this time

        Comment by Mike | November 11, 2015

  24. Just FYI guys, code is available on Github: https://github.com/martin9700/POSH-EmployeeDirectory

    Comment by Martin9700 | November 5, 2015 | Reply

  25. Been using this script for the directory for months and it’s great. We’ve noticed though new employees added the last few weeks are not showing up. we don’t see any limits in the script. Any users that were previously disabled and re-enabled (contract workers) show up ok, and changes to existing are showing fine as well. any thoughts? Is there any way to right out a log if it encounters a problem? we checked our AD and all are replicating OK. I’ve also tried it on another server in a different site where the primary DC/AD server is.

    Comment by Todd | August 6, 2016 | Reply

    • Huh, really shouldn’t be any limit, the script’s not that smart! I would recommend adding verbose transcript logging:
      https://thesurlyadmin.com/2015/10/20/transcript-logging-why-you-should-do-it/
      I’d just use the first code snippet, not the fancier stuff. At least that way you can see if something is going wrong. Also, I put the script out on GitHub so if you find the problem and want to contribute the fix, that would be awesome!

      https://github.com/martin9700/Surly.EmployeeDirectory.WebPage

      Comment by Martin9700 | August 6, 2016 | Reply

      • Good Day sir. our sys admin figured out the issue, he added $ADSearch.PageSize = 1000 to your script and now we are good to go. I guess we reached our 1000 Object in the system, so things after that were not returned.

        Comment by Todd | August 10, 2016

      • Good work!

        Comment by Martin9700 | August 10, 2016

  26. thanks for the quick reply! I’ll take a look at your suggestions and let you know if I find anything.

    Comment by Todd | August 6, 2016 | Reply

  27. I just stumbled upon this script.. Thank you very much for providing it. It runs great but I have an odd anomaly. I am searching an OU called Locations, and of course all of the sub OU’s under it. This is where all of our user accounts exist.

    The HTML page appears to show all of our users and a bit more. Meaning, it shows 3 entries but there are no first or last names to these entries. I cannot run them down to see what object this is because – well, I don’t understand how if I don’t have a user name to look for.

    Any and all assistance would be greatly appreciated.

    Comment by cwpippin | August 26, 2016 | Reply

    • I’ve seen this, but it’s been so many years since I ran this script I don’t quite remember. Check for any contacts you might have in those OU’s. It might be them.

      Comment by Martin9700 | August 26, 2016 | Reply

  28. W have our OU’s setup like this
    Domian Resources
    >> Users
    >> Plant
    >> Dept
    >> Users WIthout Phone Numbers

    Is there any way to expressly exclude the “Users without Phone Numbers” OU from each department OU? I can’t seem to figure this out. Somewhere in the script to add a -Filter parameter.

    Comment by PatKul2016 | November 21, 2016 | Reply

    • No, nothing built in, you’d have to code some exclusions in there.

      Comment by Martin9700 | November 23, 2016 | Reply

  29. This is a great script by the way. Great work.

    Comment by PatKul2016 | November 21, 2016 | Reply

  30. Hi, I have a small issue maybe someone would be able to help me with.
    I have a department named “Direction des finances et de l’administration” which is not filtered at all when I click on the button created for that purpose. I am pretty sure it has to do with the apostrophe in the name.. (other departments do not contain any and works #1)
    Does anyone know how I can fix it ? I have tried several things but my programming skills are clearly limited !!

    Comment by Sylvain | June 20, 2017 | Reply

  31. Thank you 🙂 I just need 2 changes : 1- show ipPhone in Ext filed : I replaced all TelephoneNumber with ipPhone but it’s not work – shown blank 2- Show displayName instead of First name and last name

    Comment by Ahmed Bayoumy | April 16, 2018 | Reply

  32. Hi Martin,
    Quick question, I have rearranged the fields to have First Name in the first column. I want to sort by first name but couldn’t find the sort command. Can you point me to the line where this is so I can change it from surname to first name

    Comment by AJ Jack | April 18, 2018 | Reply

    • The line you are looking for is: [string]$SortBy = “LastName”,

      You can change that to: [string]$SortBy = “FirstName”,

      Comment by Michael Zbarsky | April 24, 2018 | Reply

  33. Oops re previous comment, I meant to say I was using version 2.6

    Comment by AJ Jack | April 18, 2018 | Reply

  34. Hi All, Firstly – I am certainly not a powershell expert – justabout a noob but anyway.. Just spent a few hours trying to figure out how to exclude users that don’t have a photo so thought I’d post it here for those of you how want to do something similar..

    Edit line 555 of the original code with
    $Users = $ADSearch.FindAll() | where {$_.properties.thumbnailphoto -ne $NULL} and hopefully it will work for you..!

    Comment by Tony D | July 3, 2018 | Reply

  35. Hi All, I’m having a funny issue with this code – it seems to work for all users accounts created before Server 2012. Is there something I am missing in the search parameters? Does anyone know a good way to debug and see what dat is actually being pulled from AD. All my AD accounts have a thumbnail… The SearchOU element seems to be working and finding the right OU but not the right user accounts. For example I am in the IT_Admin OU and so is my colleague Bob. When I set the SearchOU to IT_Admin it only find me an my photo – not my colleague. I can only surmise that the info in my colleagues AD account is missing something but as I’m not a powershell guy I have no idea what it could be – Any help would be appreciated

    Comment by Tony D | July 3, 2018 | Reply

  36. Is there a was, not to have the image expand when you hover?

    Comment by Jeff Thrasher | August 23, 2018 | Reply

  37. To people having issues with image display, I’ve created a pull request on the github repository that should fix the issue and details why it was occurring:
    https://github.com/martin9700/Surly.EmployeeDirectory.WebPage/pull/2

    Comment by Malcolm | October 2, 2018 | Reply


Leave a Reply to Michael Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: