Now on AskDS!

It is my sincere honor to inform you that you may now also find me waxing poetic on the AskDS blog, Microsoft's official enterprise support blog for AD DS and more. Special thanks to the superstars (*cough*Ned*cough*) that came before me and inspired me to get to where I've always wanted to be.

Check me out!

Some DFS Diagnostics and How to Make Dfsutil.exe Portable

There was a question recently about whether DFS was setup and working properly.

Well, let me back up.  There was a problem where an application dumped a file into a file share, but for some reason, the users were unable to see that file for up to an hour after it had supposedly been dumped into the file share.  And that file share was part of a DFS replication group.  So maybe the users were having to wait on DFS replication for some reason.  DFS was suspected, but we were short on evidence and facts.

So the users were in another geographical location, in another Active Directory site.  I wanted to see which DFS replication partner was the active target from the perspective of those clients in the other site.  For example, think of when you look at the following dialog box:


All well and good, but I needed to do this through the command line... via remote administration.

And the client was Windows 7, and it did not have Powershell remoting enabled, nor did it have RSAT (Remote Server Admin Tools) installed.

So if I wanted to check this DFS information on my own machine, which does have RSAT installed, I'd simply type:

C:\> dfsutil /PktInfo

And that will dump out lots of historical DFS target info like so:

C:\> dfsutil /PktInfo
12 entries...
Entry: \Server01\SiteB\Files
ShortEntry: \Server01\SiteB\Files
Expires in 89 seconds
UseCount: 0 Type:0x1 ( DFS )
   0:[\Server02\Legacy] AccessStatus: 0 ( ACTIVE TARGETSET )
   1:[\Server03\DFSRoot\Accounting\SiteB\Files] ( TARGETSET )
Entry: \Server01\SiteB\Files ShortEntry: \Server01\SiteB\Files Expires in 97 seconds UseCount: 0 Type:0x1 ( DFS ) 0:[\Server02\Files] AccessStatus: 0 ( ACTIVE TARGETSET ) 1:[\Server03\DFSRoot\Accounting\SiteB\Files] ( TARGETSET )

And so on... but I need to see things from the perspective of a client at that other site.

So the first thing I will do is... use psexec to enable Powershell Remoting on one of the remote clients!

psexec \\pc01 powershell.exe -Command "Enable-PSRemoting -Force"

Why? Because I will bootstrap you into the new decade kicking and screaming if I have to, that's why!

Secondly, I need to transfer dfsutil.exe (and dependencies) to the destination PC.  I don't want to install the entire RSAT on the remote client just to run this single test. The only hitch here is that you must use the version of dfsutil.exe that comes from the Remote Server Administration Tools that was meant for that version of Windows. So for instance, I couldn't transfer my version of dfsutil.exe to the destination PC, because the destination PC was Windows 7 and my workstation was Windows 8.1. I had to find a Windows 7 workstation with RSAT already installed as the source.  Also, you need to transfer the language file, dfsutil.exe.mui, into System32\en-US\.  So:

dfsutil.exe     -> \\pc01\c$\windows\system32\dfsutil.exe
dfsutil.exe.mui -> \\pc01\c$\windows\system32\en-US\dfsutil.exe.mui

Now I was ready to use Enter-PSSession to start a remote session on the PC and use dfsutil to run the diagnostic test.

Send-OutlookMailMessage with Voting Options

I needed a way to programmatically send emails through Outlook that contained "voting options" ... the little buttons that let the recipients vote yes or no or whatever on a given topic.  So I whipped up a reusable cmdlet right quick.

#
Function Send-OutlookMailMessage
{
    [CmdletBinding()]
    Param([Parameter(Mandatory=$True)][String[]]$To,
          [Parameter(Mandatory=$False)][String]$Subject,
          [Parameter(Mandatory=$False)][String]$Body,
          [Parameter(Mandatory=$False)][String[]]$Attachments,
          [Parameter(Mandatory=$False)][String[]]$VotingOptions,
          [Parameter(Mandatory=$False)][ValidateSet('High','Low','Normal')][String]$Priority = 'Normal',
          [Parameter(Mandatory=$False)][Switch]$BodyAsHTML = $False)

    BEGIN
    {
        Set-StrictMode -Version Latest
        [Bool]$ShouldProcess = $True
        [Bool]$OutlookWasAlreadyRunning = $True

        Try
        {
            Get-Process Outlook -ErrorAction Stop | Out-Null
        }
        Catch
        {
            $OutlookWasAlreadyRunning = $False
        }

        Try
        {
            $Outlook = New-Object -ComObject Outlook.Application -ErrorAction Stop
            If ($Outlook -EQ $Null)
            {
                Throw 'COM Object was null.'
            }
            Write-Verbose -Message 'Outlook COM object loaded.'
        }
        Catch
        {
            Write-Error "Failed to load Outlook! ($($_.Exception.Message))"        
            $ShouldProcess = $False
        }
    }
    PROCESS
    {
        If ($ShouldProcess)
        {
            Try
            {
                $Mail = $Outlook.CreateItem(0)
                If ($Mail -EQ $Null)
                {
                    Throw
                }
                Write-Verbose -Message 'Mail item created.'
            }
            Catch
            {
                Write-Error "Failed to create mail item! ($($_.Exception.Message))"
                $ShouldProcess = $False
                Return
            }

            Foreach ($Recipient In $To)
            {
                Try
                {
                    $Mail.Recipients.Add($Recipient) | Out-Null
                }
                Catch
                {
                    Write-Error "Failed to add recipient $Recipient"
                    $ShouldProcess = $False
                    Return
                }
                Write-Verbose -Message "Added recipient $Recipient"
            }

            If ($Subject.Length -GT 0)
            {
                $Mail.Subject = $Subject
            }
            Else
            {
                Write-Warning 'No email subject was supplied.'
            }
            If ($Body.Length -GT 0)
            {
                If ($BodyAsHTML)
                {
                    $Mail.HTMLBody = $Body
                }
                Else
                {
                    $Mail.Body = $Body
                }
            }
            Else
            {
                Write-Warning 'No email body was supplied.'
            }

            Foreach ($File In $Attachments)
            {
                Try
                {
                    # TODO: Suppress warning dialog box when attempting to send an 'unsafe' attachment?
                    $Mail.Attachments.Add($File) | Out-Null
                }
                Catch
                {
                    Write-Error "Error adding $File as an attachment!"
                    $ShouldProcess = $False
                    Return
                }
            }

            If ($VotingOptions.Count -GT 0)
            {
                $Mail.VotingOptions = $VotingOptions -Join ';'
            }

            Switch ($Priority)
            {
                'High'
                { 
                    $Mail.Importance = 2
                }
                'Low' 
                { 
                    $Mail.Importance = 0
                }
                'Normal' 
                { 
                    $Mail.Importance = 1
                }
                Default 
                { 
                    Write-Error "Debug priority!"
                    $ShouldProcess = $False
                    Return
                }
            }

            $Mail.Send()
        }
    }
    END
    {
        If ($Outlook)
        {
            If (-Not($OutlookWasAlreadyRunning))
            {
                $Outlook.Quit()
            }
            [System.Runtime.Interopservices.Marshal]::ReleaseComObject($Outlook) | Out-Null
        }
    }
}

Send-OutlookMailMessage -To 'ryan@myotherpcisacloud.com' -Subject 'Can you create an Outlook poll via Powershell?' -Body "

Well, can you?

" -VotingOptions 'Yes','No','IDK' -BodyAsHTML

Poking Around DNS Scavenging Settings with Powershell

I've been working toward getting DNS scavenging turned on in a domain.  DNS scavenging, as you may know, takes a good deal of patience and forethought.  It's not something you want to just blindly enable without doing any reconnaissance first.  First off, since I'm new to this environment, let me scan all the domain controllers (which are also the DNS servers in this case) and see what the scavenging and aging settings currently look like:
 
#
$Servers = @()
ForEach ($DC In Get-ADDomainController -Filter *)
{
  $Server = New-Object PSObject -Property @{ 
     Hostname   = $DC.HostName
     Scavenging = $((Get-DnsServerScavenging `
                        -ComputerName $DC.HostName).ScavengingState)
     Aging      = $((Get-DnsServerZoneAging `
                        -Name 'acme.com' `
                        -ComputerName $DC.HostName).AgingEnabled)
    }
  $Servers += $Server
}

$Servers | FT -AutoSize
#
Hostname Scavenging Aging
-------- ---------- -----
DC01     False      True
DC02     False      True
DC03     False      True
DC04     False      True
DC05     False      True

So record aging is already turned on for the zone.  All that's left to do is enable scavenging on one of the DNS servers. (I don't like having all of the domain controllers scavenging - just one.)  But before I do that, I want to wait a while (like, a couple weeks) and see what hosts are updating their DNS records and which ones aren't. Lucky us - DNS server has a WMI provider.

#
$Records = Get-WmiObject 
              -Namespace 'Root\MicrosoftDNS' 
              -Query 'SELECT * FROM MicrosoftDNS_ResourceRecord WHERE Timestamp != 0'

$Records | Select TextRepresentation, `
           @{n='Timestamp'; e={([DateTime]'1/1/1601').AddHours($_.Timestamp)}} `
         | Where Timestamp -LT (Get-Date).AddDays(-30) | FT -AutoSize

The only tough bit is that the record's timestamp comes as a 32-bit integer that represents the number of hours elapsed since January 1st, 1601.  So you'd want to convert that into a meaningful date.  Now we can see which resource records in DNS aren't refreshing themselves on a regular basis.  After checking that list for sanity and correcting any problems, we can turn on scavenging. 

TinyWebRedirector

I wrote something this weekend.

TinyWebRedirector v1.0 - Redirects HTTP Requests
Copyright (C) 2015 Joseph Ryan Ries
www.myotherpcisacloud.com

Usage:
Install:   TinyWebRedirector -install
Uninstall: TinyWebRedirector -uninstall

I wrote this micro web server for all the sysadmins out there who have an internal Active Directory that shares the same DNS name as their public domain name. Let's say your internal AD domain name is contoso.com. Your public website is also contoso.com. In this scenario, internal employees at the office cannot reach your public website by entering http://contoso.com into their web browsers, because contoso.com internally resolves to the IP address of one of your AD domain controllers. This has lead to messy solutions, such as installing IIS on each domain controller, for the sole purpose of redirecting requests on port 80 to www.contoso.com. But installing IIS on your domain controllers is not a great idea.

TinyWebRedirector is more suited to this purpose because:

  • It is tiny. The image file is 110KB, and runs with a ~2.6MB working set. It requires no redistributable DLLs.
  • It does one thing and one thing only. This translates to a much smaller potential attack surface than a large web server such as IIS.
  • It is written in C, and so does not require .NET. Will run on any Windows machine Vista/2008 or greater.
  • The listening port (default 80) and the URL to redirect visitors to is configurable in the registry at HKLM\SYSTEM\CurrentControlSet\Services\TinyWebRedirector. Restart the service for changes to take effect.
  • The service runs as Local Service. This is a much safer configuration than services that run as Local System.

Please let me know if you find any bugs or weaknesses.

Github repository is here.

The compiled and signed x64 binary is here:

TinyWebRedirector.exe (112.8KB)