Scripts/ExternalSources/Racktables.ps1

<#
.SYNOPSIS
    Pull Racktables data, add to Neo4j

.DESCRIPTION
    Pull Racktables data, add to Neo4j

    This is invoked by Connect-TheDots

.PARAMETER Prefix
    Prefix to append to properties when we add them to Neo4j

    This helps identify properties that might come from mutiple sources, or where the source is ambiguous

    For example, row becomes RACKrow

    Defaults to RACK.

.PARAMETER MergeProperty
    We use this to correlate Server data from multiple sources

    We assume server data should correlate if the value for this on a Racktables system matches the ServerUnique value in Neo4j

    Default: NameLower. Change at your own risk

.PARAMETER Label
    What label do we assign the data we pull?

    Defaults to Server. Change at your own risk

.PARAMETER Properties
    Properties to extract and select from Racktables

.PARAMETER Excludes
    Properties to exclude (in line with transforms)

.PARAMETER Transforms
    Properties to select again (in line with excludes)

    Example:

    *, # Keep all properties from -Properties
    @{
        label='NameLower'
        expression={
            @($_.System -split " ")[0].ToLower()
        }
    }

    This is the default. It keeps all properties from -Properties, and add a calculated NameLower

.PARAMETER BaseUri
    Racktables "API" Base URI

    See http://sjoeboo.github.io/blog/2012/05/31/getting-racktables-location-info-into-puppet/ for details

.PARAMETER Domains
    Append these to make Racktables MergeProperty value fully qualified.

    E.g. if Racktables NameLower is dc01, and Domains is contoso.com and contoso2.com,
         we merge data into servers with hostname dc01.contoso.com and/or dc01.contoso2.com

.FUNCTIONALITY
    Dots
#>

[cmdletbinding()]
param(
    [string]$Prefix = 'RACK',
    [string]$MergeProperty = 'NameLower',
    [string]$Label = 'Server',
    [string[]]$Properties = @(
        'System'
        'asset',
        'row',
        'rack',
        'height',
        'ru'
    ),
    [string[]]$Excludes,
    [object[]]$Transforms = @(
        '*',
        @{
            label='NameLower'
            expression={
                @($_.System -split " ")[0].ToLower()
            }
        }
    ),
    [string]$BaseUri,
    [string[]]$Domains,
    [switch]$AllLower = $Script:AllLower
)
# Dot source so module import is available in this scope
if($script:TestMode) {
    Write-Verbose "Using mock functions from $ModuleRoot/Mock/Mocks.ps1"
    . "$ModuleRoot/Mock/Mocks.ps1"
}
else {
    function Get-RackFacts {
        <#
        .SYNOPSIS
            Query RackTables data
        .DESCRIPTION
            Query RackTables data generated via duct tapi:
            http://sjoeboo.github.io/blog/2012/05/31/getting-racktables-location-info-into-puppet/
        .EXAMPLE
            Get-RackFacts -ComputerName cepr*
            # Get all the things that start with cepr
        .EXAMPLE
            Get-RackFacts -ComputerName *
            # Get all the things
        #>

        param (
            [string]$BaseUri,
            [string[]]$ComputerName
        )
        function ConvertFrom-RackFacts {
            [cmdletbinding()]
            param(
                [string]$BaseUri,
                [string]$Name
            )
            $Raw = $null
            $Rows = $null
            $Raw = Invoke-RestMethod -Uri "$BaseUri\$name" -ErrorAction Stop
            if($Raw -is [system.string]) {
                $Rows = $Raw -split "`n"
            }
            if($Rows -and $Rows.count -gt 1) {
                $Output = @{System = [System.Web.HttpUtility]::UrlDecode($Name)}
                foreach($Row in $Rows) {
                    if($Row -match ":") {
                        $SplitRow = @($Row -split ":")
                        $Key = $SplitRow[0]
                        $Count = $SplitRow.count
                        $Value = $SplitRow[1..$Count] -join ":"
                        if($Value -is [system.string]){$Value = $Value.trim()}
                        if($key -is [system.string]){$key = $key.trim()}
                        $Output.add($Key, $Value)
                    }
                }
                [pscustomobject]$Output
            }
        }

        foreach($Name in $ComputerName) {
            # this isn't a real API, it's duct tape from a blog post - handy, but... we'll do the work.
            $SearchAll = $False
            $Wildcard = $False
            if($Name -match '\*') {
                $SearchAll = $True
                $WildCard = $True
            }
            try {
                ConvertFrom-RackFacts -BaseUri $BaseUri -Name $Name -ErrorAction Stop
            }
            catch {
                if($_.Exception.Message -match '404') {
                    $SearchAll = $True
                }
                else {
                    Write-Error $_
                }
            }

            if($SearchAll) {
                if(-not $Wildcard) {
                    $Name = "*$Name*"
                }
                $d = Invoke-WebRequest $BaseUri
                $MatchingLinks = $d.links.where({$_.href -notmatch "^\?|^/rack" -and $_.href -like $Name})
                foreach($Link in $MatchingLinks) {
                    ConvertFrom-RackFacts -BaseUri $BaseUri -Name $Link.href
                }
            }
        }
    }
}

$Unique = "${Prefix}${MergeProperty}"
$Date = Get-Date

$Nodes = Get-RackFacts -BaseUri $BaseUri -ComputerName *
$Nodes = $Nodes |
        Select-Object -Property $Properties |
        Select-Object -Property $Transforms

$names = $nodes.NameLower
$NameHash = @{}
$Dupes = foreach($Name in $Names) {
    if(-not $NameHash.ContainsKey($Name)){
        $NameHash.Add($Name,$null)
    }
    else {
        $Name
    }
}

$Nodes = Foreach($Node in $Nodes.where({$Dupes -notcontains $_.NameLower})) {
    $Output = Add-PropertyPrefix -Prefix $Prefix -Object $Node
    Add-Member -InputObject $Output -MemberType NoteProperty -Name "${script:CMDBPrefix}${Prefix}UpdateDate" -Value $Date -Force
    if($AllLower) {
        ConvertTo-Lower -InputObject $Output    
    }
    $Output
}

$TotalCount = $Nodes.count
$Count = 0
Foreach($Node in $Nodes) {
    Write-Progress -Activity "Updating Neo4j" -Status  "Adding $($Node.$Unique)" -PercentComplete (($Count / $TotalCount)*100)
    $Count++
    foreach($Domain in $Domains) {
        # Update existing, do not create new nodes, data is garbage
        Set-Neo4jNode -Label $Label -Hash @{$script:ServerUnique = "$($Node.$Unique).$Domain"} -InputObject $Node -NoCreate
    }
}