stephen lam

blog about nothing

Splatting in PowerShell
2017-MAY-02

While changing all scripts that touches Active Directory from using Quest ActiveDirectory module to the built in ActiveDirectory module I discovered Splatting. Apparently this has been around since PowerShell v2, so perhaps other PowerShell users aren't aware that this exists, like me.

This technique allows a code like this:

Get-ADUser -Filter * -Properties * | Select-Object 'Name',
 @{Name = "Username"; Expression = {$_.SamAccountName}}, @{Name = "Title"; Expression = {$_.Title}}, @{Name = "Title (FR)"; Expression = {$_.ExtensionAttribute1}}, @{Name = "Department "; Expression = {$_.Department}}, @{Name = "Department (FR)"; Expression = {$_.ExtensionAttribute2}}, @{Name = "Accreditation"; Expression = {$_.ExtensionAttribute3}}

to something that's more readable like this:

$Properties = @{
   Property = 'Name',
   @{Name = "Username"; Expression = {$_.SamAccountName}},
   @{Name = "Title"; Expression = {$_.Title}},
   @{Name = "Title (FR)"; Expression = {$_.ExtensionAttribute1}},
   @{Name = "Department "; Expression = {$_.Department}},
   @{Name = "Department (FR)"; Expression = {$_.ExtensionAttribute2}},
   @{Name = "Accreditation"; Expression = {$_.ExtensionAttribute3}}
   }
Get-ADUser -Filter * -Properties * | Select-Object @Properties

It makes it super easy to take a look at what I'm trying to do here, and the best part is that it makes it so easy to edit. Give it a try in your own script.

READ
PowerShell Account Expiration
2017-MAR-09

While writing an user administration script to add users, I noticed that all my new created temporary workers had the wrong Account Expiration date on them, they were all off by a day.

The code in question is the following:

Set-ADUser -Identity $samName -AccountExpirationDate (Get-Date $endDate) -Credential $creds

So what happens is when we pass the $endDate (ie: March 09, 2017) variable it actually passes March 09, 2017 12:00:00 AM. So when we look at the user object with in Active Directory Users and Computers, you'll see that the account expires end of day March 8th. Which makes total sense, but we actually want the account to expire end of day March 9th. So the easiest fix is to do the do this:

Set-ADUser -Identity $samName -AccountExpirationDate ((Get-Date $endDate).AddDays(1)) -Credential $creds

Such a small little thing that I would never have noticed, but when you think about it. It makes a lot of sense why it did what it did, and what we have to do to compensate for it.

READ
Funny thingy about Format-Wide for PowerShell
2017-FEB-15

I had created the following function a while back to display an Array of String in a nice way through columns, and not a long list as the default for Write-Host.

function format-displaylist{
<#
 .DESCRIPTION
  Display an array larger than certain members into nice column(s) for
  easy readability
#>
 param(
  [Parameter(Mandatory=$true,
   ValueFromPipeline=$true)]
  $arrayList,
  [Parameter(Mandatory=$false,
   ValueFromPipeline=$true,
   HelpMessage="Use the name properly of a multi-dimension array")]
  [switch]$displayName
 )
 switch($arrayList.Count){
  {($_ -le 10)}{$column = 1}
  {($_ -gt 10) -and ($_ -le 15)}{$column = 2}
  default{$column = 3}
 }
 $tempArr = @()
 $tempInx = 1
 switch($displayName){
  $false {
   foreach($item in $arrayList){
    $tempArr += "$tempInx) $item"
    $tempInx = $tempInx + 1
   }
  }
  $true {
   foreach($item in $arrayList){
    $tempArr += "$tempInx) " + $item.Name
    $tempInx = $tempInx + 1
   }
  }
 }
 Write-Host ($tempArr | Format-Wide {$_} -Column $column -Force | Out-String)
}

So the issue with that function is that it adds 3 carriage return to the last line, and made the whole thing look ridiculously ugly looking. It bother me, but I didn't have time or willingness to take a look into why it would do that. That is, until today. While writing a new script, I was constantly calling this function and the displaying of the 3 carriage return was driving me mad. I decide to delve in and see what's causing it, and what I can do to resolve it.

After trying numerous other things, I noticed that I was Out-String, and I thought to myself why I was doing that. I'm not planning to work with the string, I just want to display the string itself. So I replaced:

Write-Host ($tempArr | Format-Wide {$_} -Column $column -Force | Out-String)

with

$tempArr | Format-Wide {$_} -Column $column -Force | Out-Host

and it gave me an output that I'm happy with. There's still return carriage (2), but it's something I can live with.

READ
Configuring nginx reverse proxy for Exchange 2010
2016-NOV-16

This will be a quick post that I hope other might find usefully when deploying nginx as a reverse proxy for Exchange 2010 SP3. With my particular setup, we had two reverse proxy servers; one on the DMZ, and the other was on the LAN and firewall rules that only allow them to communicate with each other through standard HTTP/S ports.

Here's quick dirty diagram of the final setup:

Internet -> Firewall -> DMZ -> Reverse Proxy 1 -> Firewall -> Reverse Proxy 2 -> Exchange

nginx conf

Reverse proxy 1

server {
        listen 443 ssl;
        server_name outlookserver.com;
        ssl_certificate /path/to/outlookserver.crt;
        ssl_certificate_key /path/to/outlookserver.key;
        ssl_prefer_server_ciphers On;
        ssl_protocols TLSv1.1 TLSv1.2;
        ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
        ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
        ssl_dhparam /path/to/dhparam.pem;
        ssl_session_cache shared:SSL:10m;
        add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
        add_header X-Content-Type-Options nosniff;
        add_header X-Frame-Options ALLOW;

        location / {
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;

                # Fix the "It appears that your reverse proxy set up is broken" error.
                proxy_pass https://reverseproxy2;
                proxy_read_timeout 360;

                error_log /var/log/nginx/owa-ssl-error.log;
                access_log /var/log/nginx/owa-ssl-access.log;
    }
}

Reverse proxy 1

server {
        server_name outlookserver.com;
        listen 443;

        ssl on;
        ssl_certificate /path/to/outlookserver.crt;
        ssl_certificate_key /path/to/outlookserver.key;
        ssl_session_timeout 5m;
        location / {
                return 301 https://outlookserver.com/owa;
        }

        proxy_http_version 1.1;
        proxy_read_timeout 360;
        proxy_pass_header Date;
        proxy_pass_header Server;
        proxy_pass_header Authorization;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass_request_headers on;
        more_set_input_headers 'Authorization: $http_authorization';
        proxy_set_header Accept-Encoding "";
        more_set_headers -s 401 'WWW-Authenticate: Basic realm="outlookserver.com"';
        # proxy_request_buffering off;
        proxy_buffering off;
        proxy_set_header Connection "Keep-Alive";

        location ~* ^/owa { proxy_pass https://exchangeserver; }
        location ~* ^/Microsoft-Server-ActiveSync { proxy_pass https://exchangeserver; }
        location ~* ^/ecp { proxy_pass https://exchangeserver; }
        location ~* ^/rpc { proxy_pass https://exchangeserver; }

        error_log /var/log/nginx/owa-ssl-error.log;
        access_log /var/log/nginx/owa-ssl-access.log;
}
READ
PowerShell script to check if computer is online
2016-OCT-26

I wrote this PowerShell function a while back. I might have a better/newer version of this somewhere, if I find it, I'll update this post with it. With that said, this requires the Quest ActiveRoles cmdlets. I don't like what Dell is doing with the Quest cmdlets, but at least you can find version 1.51 download still for free.

<#
    .SYNOPSIS
        Take an IP address or a computer name as input and test to see if that
        computer is valid, and if it's pingable

    .DESCRIPTION
        Takes an input and does some validation on the input

    .EXAMPLE
        Using with an IP address
        Check-Computer 192.168.1.1

    .EXAMPLE
        Using with a computer name
        Check-Computer ott-0001

    .NOTES
        Stephen Lam
#>
function Check-Computer ($ipOrCompName)
{
    #//Add Quest CMDlets as it's needed for this script
    Add-PSSnapin Quest.ActiveRoles.ADManagement -ErrorAction SilentlyContinue

    if($ipOrCompName -ne $null)
    {
        #//Test to see if the $ipOrCompName is a valid IP format
        #//IP is in a valid IP format; it will use Test-Connection CMDlet to see
        #//if that IP is pingable
        if([bool]($ipOrCompName -as [Net.IPAddress])){
            if(Test-Connection -ComputerName $ipOrCompName -Count 1 -Quiet -ErrorAction SilentlyContinue){
                $compName = [System.Net.Dns]::GetHostEntry($ipOrCompName).HostName
            }
            else{
                #//Couldn't find a computer that's base on the IP address
                $compName = $null
            }
        }

        #//Since the input is not in a valid IP format, it will test to see if this is or
        #//a part of a computer name
        else{
            #//Searches the whole AD for computer that starts with $ipOrCompName
            $compTest = Get-QADComputer -Identity $ipOrCompName*

            #//Checks to see if there's more than one computer object that returns from the search
            #//If there is, it will either list all the computer it found and allow the user to
            #//select the computer they want, or if it's just one computer that returns from the
            #//search, it will use that one automatically
            if ($compTest -ne $null){
                #//More than one computer object found with that search criteria
                If($compTest.count -gt 1){
                    $counter = 1
                    Write-Host "Found" $compTest.Count "that matches" $ipOrCompName
                    foreach($obj in $compTest){
                        Write-Host "$counter)" $obj.Name
                        $counter++
                    }
                    $select = Read-Host "Please enter the your selection (eg. 1 for" $compTest[0].Name")"

                    #//Checks to see if the selected computer is pingable/online
                    if(Test-Connection -ComputerName $compTest[$select-1].Name -Count 1 -Quiet){
                        $compName = $compTest[$select-1].Name
                    }
                    else{
                        #//Couldn't ping the computer
                        $compName = $null
                    }
                }

                #//Only one computer object found in the AD that matches the criteria
                else{
                    #//Checks to see if the selected computer is pingable/online
                    if(Test-Connection -ComputerName $compTest.Name -Count 1 -Quiet -ErrorAction SilentlyContinue){
                        $compName = $compTest.Name
                    }
                    else{
                        $compName = $null
                    }
                }
            }

            else{
                $compName = $null
            }
        }
    }
    else{
        #//No computer found base on the IPorCompName
        $compName = $null
    }
    #//Returns the $compName either with a name or $null if it did not find anything
    return $compName
}
READ
OLDER
1 of 3