Welcome to BlogEngine.NET 3.1

If you see this post it means that BlogEngine.NET is running and the hard part of creating your own blog is done. There is only a few things left to do.

Write Permissions

To be able to log in, write posts and customize blog, you need to enable write permissions on the App_Data and Custom folders. If your blog is hosted at a hosting provider, you can either log into your account’s admin page or call the support.

If you wish to use a database to store your blog data, we still encourage you to enable this write access for an images you may wish to store for your blog posts.  If you are interested in using Microsoft SQL Server, MySQL, SQL CE, or other databases, please see the BlogEngine docs to get started.

Security

When you've got write permissions set, you need to change the username and password. Find the sign-in link located either at the bottom or top of the page depending on your current theme and click it. Now enter "admin" in both the username and password fields and click the button. You will now see an admin menu appear. It has a link to the "Users" admin page. From there you can change password, create new users and set roles and permissions. Passwords are hashed by default so you better configure email in settings for password recovery to work or learn how to do it manually.

Configuration and Profile

Now that you have your blog secured, take a look through the settings and give your new blog a title.  BlogEngine.NET is set up to take full advantage of many semantic formats and technologies such as FOAF, SIOC and APML. It means that the content stored in your BlogEngine.NET installation will be fully portable and auto-discoverable.  Be sure to fill in your author profile to take better advantage of this.

Themes, Widgets & Extensions

One last thing to consider is customizing the look and behavior of your blog. We have themes, widgets and extensions available right out of the box. You can install more right from admin panel under Custom/Gallery.

On the web

You can find news about BlogEngine.NET on the official website. For tutorials, documentation, tips and tricks visit our docs site. The ongoing development of BlogEngine.NET can be followed at CodePlex where the daily builds will be published for anyone to download.

Good luck and happy writing.

The BlogEngine.NET team

CustomAddADUser v1.0

I uploaded a new project on Github today, named CustomAddADUser.

If you have a lot of Active Directories and/or employee account records to maintain, or even if you don't but you're just obsessive compulsive like me, you might require a certain level of completeness, accuracy, and use of custom attributes that the old Active Directory Users and Computers doesn't really give you.  For instance, let's say that your HR system requires that you populate the "Employee ID" attribute on your user accounts.  The ADUC GUI doesn't provide that as part of the "new user" dialog. You have to create the user first, then enable "Advanced Features," then go and click on them again, open their properties sheet, go to the "Attribute Editor" tab, and type it in there.  And even then it's still prone to typos, which will make your identity management a struggle and your HR system won't be able to accurately track the user accounts.  You can't just mark the "employeeID" attribute as mandatory unless you want to modify the AD schema. And even if you did that, you still can't ensure that the employee ID matches a very particular ID format that your company uses.

Well CustomAddADUser aims to make all that possible.

Almost everything is customizable via a configuration file, including which attributes are mandatory, the application's icon, the window title, the company logo that appears on the "About" tab, the help text that appears on the About tab, and the regular expressions that are used to validate the input. Furthermore, you'll notice as you enter the user's account details that names are automatically capitalized and trimmed for you, etc., to encourage a clean and consistent user database.

(Gah people that don't capitalize the first letters of names drives me up the wall!)

So let's say that you need all your employees to have their employee ID attributes filled out, and your company uses employee IDs that look like F4348277 for full-time employees, and P4348277 for part time employees.  No problem, just edit the config file to use this regex pattern:

<add key="employeeIDRegex" value="\b[fp]\d{7}\b" />

Now the application will not allow the user to be created until the employee ID matches that regex pattern.  It will politely remind the administrator that the attribute needs to match that pattern.

All the other attributes have their own regex patterns too. If you don't care about the format of the attribute, just leave the regex pattern as (.+) to match anything.

Additionally, since it's very rare that AD users are created and aren't assigned to any security groups, you can easily copy the security group members from another existing user during creation.  When you change the "Create in" drop-down list, the list of available users from which to copy group memberships changes accordingly to show only users who are also in that branch of the directory.

The app is about 36 hours old so I will likely continue adding new features pretty quickly.  And of course, I wouldn't have put it on Github if I wasn't welcoming to anyone who wanted to submit bugs, feature requests, etc.  One of my missions with this application is to make it significantly better than the standard ADUC Users and Computers interface that people might actually want to use it... so I will be adding more features to it.

Writing and Distributing a Powershell Module via GPO

I recently wrote a bunch of Powershell Cmdlets that were related to each other, so I decided to package them all up together as a Powershell module.  These particular Cmdlets were tightly integrated with Active Directory, so it made sense that they would often be run on a domain controller, or against a domain controller, usually by a domain administrator.

First, to create a Powershell script module, you create a folder, and then place your *.psm1 and *.psd1 files in that folder and they must have exactly the same name as that folder. (You can also compile your Powershell module into a DLL, but let's just talk about script modules today.)  So for instance, if you name your module "CloudModule," you would create a directory such as:

C:\Program Files\PSModules\CloudModule\

And then you'd place your files of the same name in that folder:

C:\Program Files\PSModules\CloudModule\CloudModule.psm1
C:\Program Files\PSModules\CloudModule\CloudModule.psd1

I think that a subdirectory of Program Files works well, because the Program Files directory is protected from modification by non-administrators and low-integrity processes.

The psd1 file is the manifest for the PS module. You can easily create a new manifest template with the New-ModuleManifest cmdlet, and customize the template to suit your needs. The module's manifest is simply the "metadata" for the Powershell module, such as what version of Powershell it requires, other prerequisite modules it requires, the module's author, etc. The cool thing is that with current versions of Powershell, just by saying:

RequiredModules = @('ActiveDirectory')

in your manifest file, Powershell will automatically load the ActiveDirectory module for you the first time any cmdlet from your module is run. So you really don't need to put an Import-Module or a #Requires -Module anywhere.

The psm1 file is the collection of Powershell Advanced Functions (cmdlets) that make up your module. I would recommend naming all of your cmdlets with a common theme, using supported verbs (Get-Verb) and a common prefix. You know how the Active Directory module does Get-ADUser, Remove-ADObject, Set-ADGroup, etc.? You should do that too. For example, if you work for McDonald's, do Show-McSalary, Deny-McWorkersComp, Stop-McStrike, etc.

Now that you've got your module directory and files laid out, you need to add that path to your PSModulePath environment variable so that Powershell knows where to look for it every time it starts. Do not try to cheat and put your custom module in the same System32 directory where Microsoft puts their standard PS modules.

I decided that I wanted all my domain controllers to automatically load this custom module. And I don't want to go install and maintain this this thing on 50 separate machines.  So for a centrally-managed solution, let's utilize Group Policy and SYSVOL.

First, let's put something like this in the Default Domain Controllers (or equivalent) GPO:


(*Click to enlarge*)

And secondly, let's put our Powershell module in:

C:\Windows\SYSVOL\sysvol\Contoso.com\scripts\PSModules\CloudModule\

Of course, since this directory is replicated amongst all domain controllers, we only need to do this on one domain controller for the files to become available on all domain controllers.  Likewise, any time you update the module, you only need to update it on one DC, and all DCs will get the update.

Lastly, since this Powershell module is only for Domain Administrators, and SYSVOL by design is a very public place, let's protect our module's directory from prying eyes:

Careful that you only modify the permissions of that one specific module directory... you don't want to modify the permissions of anything else in Sysvol.

Parse-DNSDebugLogFile

Happy Friday, nerds.

This is just going to be a quick post wherein I paste in a Powershell script I wrote about a year ago that parses a Windows Server DNS debug log file, and converts the log file from flat boring hard-to-read text, into lovely Powershell object output.  This makes sorting, analyzing and data mining your DNS log files with Powershell a breeze.

It's not my best work, but I wanted to get it posted on here because I forgot about it until today, and I just remembered it when I wanted to parse a DNS log file, and I tried searching for it here and realized that I never posted it.

#Requires -Version 2
Function Parse-DNSDebugLogFile
{
<#
.SYNOPSIS
    This function reads a Windows Server DNS debug log file, and turns it from
    a big flat file of text, into a useful collection of objects, with each
    object in the collection representing one send or receive packet processed
    by the DNS server.
.DESCRIPTION
    This function reads a Windows Server DNS debug log file, and turns it from
    a big flat file of text, into a useful collection of objects, with each
    object in the collection representing one send or receive packet processed
    by the DNS server. This function takes two parameters - DNSLog and ErrorLog.
    Defaults are used if no parameters are supplied.
.NOTES
    Written by Ryan Ries, June 2013.
#>
    Param([Parameter(Mandatory=$True, Position=1)]
            [ValidateScript({Test-Path $_ -PathType Leaf})]
            [String]$DNSLog,
          [Parameter()]
            [String]$ErrorLog = "$Env:USERPROFILE\Desktop\Parse-DNSDebugLogFile.Errors.log")

    # This is the collection of DNS Query objects that this function will eventually return.
    $DNSQueriesCollection = @()
    # Try to read in the DNS debug log file. If error occurs, log error and return null.
    Try
    {
        [System.Array]$DNSLogFile = Get-Content $DNSLog -ReadCount 0 -ErrorAction Stop
        If($DNSLogFile.Count -LT 30)
        {
            Throw "File was empty."
        }
    }
    Catch
    {
        "$(Get-Date) - $($_.Exception.Message)" | Out-File $ErrorLog -Append
        Write-Verbose $_.Exception.Message
        Return
    }
    
    # Cut off the header of the DNS debug log file. It's about 30 lines long and we don't want it.
    $DNSLogFile = $DNSLogFile[30..($DNSLogFile.Count - 1)]
    # Create a temporary buffer for storing only lines that look valid.
    $Buffer = @()
    # Loop through log file. If the line is not blank and it contains what looks like a numerical date,
    # then we can be reasonably sure that it's a valid log file line. If the line is valid,
    # then add it to the buffer.    
    Foreach($_ In $DNSLogFile)
    {
        If($_.Length -GT 0 -AND $_ -Match "\d+/\d+/")
        {
            $Buffer += $_
        }
    }
    # Dump the buffer back into the DNSLogFile variable, and clear the buffer.
    $DNSLogFile = $Buffer
    $Buffer     = $Null
    # Now we parse text and use it to assemble a Query object. If all goes well,
    # then we add the Query object to the Query object collection. This is nasty-looking
    # stuff that we have to do to turn a flat text file into a beutiful collection of
    # objects with members.
    Foreach($_ In $DNSLogFile)
    {
        If($_.Contains(" PACKET "))
        {
            $Query = New-Object System.Object
            $Query | Add-Member -Type NoteProperty -Name Date      -Value $_.Split(' ')[0]
            $Query | Add-Member -Type NoteProperty -Name Time      -Value $_.Split(' ')[1]
            $Query | Add-Member -Type NoteProperty -Name AMPM      -Value $_.Split(' ')[2]
            $Query | Add-Member -Type NoteProperty -Name ThreadID  -Value $_.Split(' ')[3]
            $Query | Add-Member -Type NoteProperty -Name PacketID  -Value $_.Split(' ')[6]
            $Query | Add-Member -Type NoteProperty -Name Protocol  -Value $_.Split(' ')[7]
            $Query | Add-Member -Type NoteProperty -Name Direction -Value $_.Split(' ')[8]
            $Query | Add-Member -Type NoteProperty -Name RemoteIP  -Value $_.Split(' ')[9]
            $BracketLeft  = $_.Split('[')[0]
            $BracketRight = $_.Split('[')[1]
            $Query | Add-Member -Type NoteProperty -Name XID       -Value $BracketLeft.Substring($BracketLeft.Length - 9, 4)
            If($BracketLeft.Substring($BracketLeft.Length - 4, 1) -EQ "R")
            {
                $Query | Add-Member -Type NoteProperty -Name Response -Value $True
            }
            Else
            {
                $Query | Add-Member -Type NoteProperty -Name Response -Value $False
            }
            $Query | Add-Member -Type NoteProperty -Name OpCode -Value $BracketLeft.Substring($BracketLeft.Length - 2, 1)
            $Query | Add-Member -Type NoteProperty -Name ResponseCode -Value $BracketRight.SubString(10, 8).Trim()
            $Query | Add-Member -Type NoteProperty -Name QuestionType -Value $_.Split(']')[1].Substring(1,5).Trim()
            $Query | Add-Member -Type NoteProperty -Name QuestionName -Value $_.Split(' ')[-1]
            
            # Just doing some more sanity checks here to make sure we parsed all that text correctly.
            # If something looks wrong, we'll just discard this whole line and continue on
            # with the next line in the log file.
            If($Query.QuestionName.Length -LT 1)
            {
                Continue
            }
            If($Query.XID.Length -NE 4)
            {
                "$(Get-Date) - XID Parse Error. The line was: $_" | Out-File $ErrorLog -Append
                Continue
            }
            If($Query.Protocol.ToUpper() -NE "UDP" -AND $Query.Protocol.ToUpper() -NE "TCP")
            {
                "$(Get-Date) - Protocol Parse Error. The line was: $_" | Out-File $ErrorLog -Append
                Continue
            }
            If($Query.Direction.ToUpper() -NE "SND" -AND $Query.Direction.ToUpper() -NE "RCV")
            {
                "$(Get-Date) - Direction Parse Error. The line was: $_" | Out-File $ErrorLog -Append
                Continue
            }
            If($Query.QuestionName.Length -LT 1)
            {
                Continue
            }
            
            # Let's change the query format back from RFC 1035 section 4.1.2 style, to the 
            # dot notation that we're used to. (8)computer(6)domain(3)com(0) = computer.domain.com.
            $Query.QuestionName = $($Query.QuestionName -Replace "\(\d*\)", ".").TrimStart('.')
            
            # Finally let's add one more property to the object; it might come in handy:
            $Query | Add-Member -Type NoteProperty -Name HostName -Value $Query.QuestionName.Split('.')[0].ToLower()
            
            # The line, which we've now converted to a query object, looks good, so let's
            # add it to the query objects collection.
            $DNSQueriesCollection += $Query
        }
    }
    $DNSLogFile = $Null
    
    # Clear the error log if it's too big.
    If(Test-Path $ErrorLog)
    {
        If((Get-ChildItem $ErrorLog).Length -GT 1MB)
        {
            Clear-Content $ErrorLog
        }
    }
    
    Return $DNSQueriesCollection
}
# END FUNCTION Parse-DNSDebugLogFile

And the output looks like this:

Date         : 6/17/2013
Time         : 11:51:41
AMPM         : AM
ThreadID     : 19BC
PacketID     : 0000000010146F10
Protocol     : UDP
Direction    : Snd
RemoteIP     : 10.173.16.82
XID          : e230
Response     : True
OpCode       : Q
ResponseCode : NOERROR
QuestionType : A
QuestionName : host01.domain.com.
HostName     : host01

Date         : 6/17/2013
Time         : 11:51:41
AMPM         : AM
ThreadID     : 19BC
PacketID     : 000000000811A6C0
Protocol     : UDP
Direction    : Snd
RemoteIP     : 10.173.16.82
XID          : 36df
Response     : True
OpCode       : Q
ResponseCode : NXDOMAIN
QuestionType : SRV
QuestionName : _kerberos-master._udp.domain.com.
HostName     : _kerberos-master

TechEd 2014

I have been wanting to attend TechEd for several years now, and finally I've attended my first one this year in Houston, Texas. Overall it was a great experience, although I was a little overwhelmed by having so much that I wanted to see and do and not enough time to do it all. I also feel disappointed that there was an emergency back home while I was at TechEd so I had to cut the trip short and miss an entire day of the conference. But, it's not the end of the world. I hope to be able to go back for TechEd 2015 next year.

Here are a few of my thoughts.

TechEd 2014 George R Brown Convention Center

I drove to Houston from Dallas, and arrived here at the George R Brown convention center on about 1 hour of sleep. Luckily I signed up as soon as registration opened, which meant that I was able to book a hotel room in the Hilton right across the street. All I had to do to get back and forth from my room to the convention center was cross the catwalk.


Monday's Keynote

It was extremely crowded. This conference was not for the agoraphobic. In fact, the conference was so congested, and the convention center so poorly designed to handle such a large crowd, it made it difficult to even move around at times. Many of the sessions (especially the Russinovich sessions) filled up 30 minutes before they were scheduled to begin. This was the first year Microsoft consolidated the MMS and TechEd conferences though, which did not help.

The conference center was a gigantic cuboid structure with no thought given to traffic flow, restroom layout, etc.

Endless hallway of TechEd conferences

That being said, the content was still great. Tons of cutting-edge System Center, Powershell Desired State Configuration, Azure stuff, and lots of security talks. (AKA "Trustworthy Computing") Lots, and lots of security talks. I almost lost count of how many times I got to see the presenters try to scare us by showing us Mimikatz to dump lsass.exe private memory, or export "non-exportable" private keys. And all the rockstars were there too; the guys that are essentially celebrities to me because of the awesome stuff they've done while at Microsoft.

Some of the other great content I saw:

  • Virtual Machine Manager Network Virtualization, which neatly solves the overlapping IP address situation in a multi-tenant environment.
  • Freaking Azure Pack which allows you to essentially deploy your very own private Azure inside your own organization.
  • Aaron Margosis trying to convince us to go up to Mark Russinovich and say to him, "Mr. Cogswell, I'm a huge fan of your work..."
  • Jeffrey Snover muttering "god damnit" to himself over and over again under his breath while trying to show us how to change the color of error text in our Powershell console.
  • Getting to pitch my idea to one of the Windows client security guys about LogonUI.exe performing a hash check on utilman.exe before launching it from the Accessibility button.
  • Etc.

Just another shot of the huge crowd
Just another shot of the endless sea of people.

High-tech signs
And an example of the high-tech digital signage.


Me and Ed Wilson
Me and Ed Wilson, AKA Dr. Scripto


Me and Mathias, whom I met on ServerFault, after a night of exploring the Tech Expo, drinking beer, walking, talking about the pathetic state of healthcare in the United States, and eating at the Cheesecake Factory.

Speaking of tech expo, this year it was all about SSDs, flash memory, ridiculous amounts of RAM for all your SQL 2014 In-Memory OLTP processing, and 56 gigabit Infiniband RoCE setups:





I hope to see you again next year, TechEd...