AD based GUID & OU based Environments/roles

Howdy all, I figured I’d share the fruits of my “learning powershell the hard way with DSC”. By no means is this my script alone, it is very much largely taken from a variety of internet sources! Thanks powershell.org :slight_smile:

First, to see how this is structured in AD:

#Pull computer objects for AD
function GetAllServers {
    import-module ActiveDirectory
    Get-ADComputer -SearchBase "OU=DSCdev,DC=subdomain,DC=contoso,DC=com" -Filter *
}
$AllServers = GetAllServers

# Gather AllNodes and add an environment variable to it based on OU name.
$AllServerConfigData = @{
    AllNodes = @(
        foreach ($node in $AllServers) {
    IF ($node.DistinguishedName -like "*OU=Prod*") {@{NodeName = $node.Name; NodeGUID = $node.objectGUID; NodeEnvironment = 'Production'}} 
    ELSEIF ($node.DistinguishedName -like "*OU=Stage*") {@{NodeName = $node.Name; NodeGUID = $node.objectGUID; NodeEnvironment = 'Stage'}} 
    ELSEIF ($node.DistinguishedName -like "*OU=QA*") {@{NodeName = $node.Name; NodeGUID = $node.objectGUID; NodeEnvironment = 'QA'}}
    ELSEIF ($node.DistinguishedName -like "*OU=DEV*") {@{NodeName = $node.Name; NodeGUID = $node.objectGUID; NodeEnvironment = 'DEV'}} 
        }
    )
}

As for roles, you can follow the same logic above to create a new subtree for “web servers” and apply roles to the above. (going to do this soon!)

Out of curiosity from the powershell pros out there, is there a ‘cleaner’ way of doing this?

Enjoy for those who want to duplicate my setup, and thanks from those who offer their guidance! :slight_smile:

I’d eliminate the duplicated parts inside that foreach loop, and put the part that’s different (NodeEnvironment) into a function. Something like this:

#Pull computer objects for AD
function GetAllServers {
    import-module ActiveDirectory
    Get-ADComputer -SearchBase "OU=DSCdev,DC=subdomain,DC=contoso,DC=com" -Filter *
}

function Get-NodeEnvironment {
    [OutputType([string])]
    param (
        [Parameter(Mandatory)]
        [string] $DistinguishedName
    )

    switch -Wildcard ($DistinguishedName) {
        '*OU=Stage*' { return 'Stage' }
        '*OU=QA*'    { return 'QA' }
        '*OU=DEV*'   { return 'DEV' }
        default      { return 'Production' }
    }
}

$AllServers = GetAllServers

# Gather AllNodes and add an environment variable to it based on OU name.
$AllServerConfigData = @{
    AllNodes = @(
        foreach ($node in $AllServers) {
            @{
                NodeName        = $node.Name
                NodeGUID        = $node.objectGUID
                NodeEnvironment = Get-NodeEnvironment -DistinguishedName $node.DistinguishedName
            }
        }
    )
}

One other thing to note is that if you’re planning to use a DSC pull server, you should be assigning the GUID to the NodeName property in your configuration data. You can store the hostname in another property if you’d like, but DSC itself is only concerned with NodeName. If you use the computer name as NodeName, then you’ll wind up with a bunch of ComputerName.MOF files instead of GUID.MOF, and will have to rename them manually before publishing to your pull server.

This is just fantastic Dave! Exactly what I was going for, but my experience with paramaters is lacking, and wildcard switching is entirely new to me hehe.

I do certainly intend to use a pull server and am aware of the requirements to use the GUID name. As of now this is being used in a ‘proof of concept’ and I’ll likely soon switch it up as you’re suggesting so that I don’t have to go through a renaming process.

I have just one question, when you switched wildcards why do you define “default” rather than continue with the structure of 'ou=???" to return ‘production’? I tested it as you presented and it works, but am just curious of the implications of doing it this way.

That was just personal preference. I decided to have the function always return something, and picked Production to be the default. Alternatively, I could have done a pattern match to return Production, and had a default of ‘Unknown’, or something along those lines.

I agree completely that it should default to something. Case in point, I just duplicated your environment function to create a “get-NodeRole” function:

function Get-NodeRole {
    [OutputType([string])]
    param (
        [Parameter(Mandatory)]
        [string] $DistinguishedName
    )
 
    switch -Wildcard ($DistinguishedName) {
        '*CN=*IIS*' { return 'WebServer' }
        '*CN=*DC*'    { return 'ADDS' }
        '*CN=*SQL*'   { return 'SQL' }
        default      { return 'Vanila' }
    }
}

This way, if my server happens to not conform to an existing/defined server role I still have a “vanila” server role, and can configure defaults for servers whose role is ‘vanila’

Awesome stuffs! :slight_smile:

There’s a subtle gotcha in that second function you posted:

$pattern = '*CN=*DC*'
$distinguishedName = 'CN=Whatever,DC=domain,DC=com'

$distinguishedName -like $pattern # True

Those * characters will match anything, including the commas that separate the distinguished name into its components. That wasn’t a problem in the original code because there was no * between “OU=” and the literal text (“Stage”, etc).

If you only want to pattern match the CN (which will always be the first element in the DN), you could do something like this:

function Get-NodeRole {
    [OutputType([string])]
    param (
        [Parameter(Mandatory)]
        [string] $DistinguishedName
    )

    $cn = ($DistinguishedName -split ',')[0]
 
    switch -Wildcard ($cn) {
        '*IIS*' { return 'WebServer' }
        '*DC*'  { return 'ADDS' }
        '*SQL*' { return 'SQL' }
        default { return 'Vanila' }
    }
}

You could also get into regular expressions, but those are more complicated than wildcards, and might be more confusing at first. In this case, a split-then-wildcard approach is probably fine.

Gold :slight_smile: Pure gold. Period :slight_smile:

Thanks!